@tailor-platform/sdk 1.32.0 → 1.33.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 (88) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/{application-B8vok3w2.mjs → application-CfAom-vi.mjs} +38 -29
  3. package/dist/application-CfAom-vi.mjs.map +1 -0
  4. package/dist/application-ChVyhwe-.mjs +12 -0
  5. package/dist/{brand-GZnI4eYb.mjs → brand-0SscafcY.mjs} +2 -1
  6. package/dist/{brand-GZnI4eYb.mjs.map → brand-0SscafcY.mjs.map} +1 -1
  7. package/dist/{chunk-DEt8GZDa.mjs → chunk-COzJYswC.mjs} +1 -0
  8. package/dist/cli/index.d.mts +1 -0
  9. package/dist/cli/index.mjs +30 -29
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/lib.d.mts +10 -6
  12. package/dist/cli/lib.mjs +14 -13
  13. package/dist/cli/lib.mjs.map +1 -1
  14. package/dist/cli/skills.d.mts +1 -0
  15. package/dist/cli/skills.mjs +2 -1
  16. package/dist/cli/skills.mjs.map +1 -1
  17. package/dist/{client-DfdgRZlQ.mjs → client-D5P1myz0.mjs} +6 -5
  18. package/dist/{client-DfdgRZlQ.mjs.map → client-D5P1myz0.mjs.map} +1 -1
  19. package/dist/{client-B0wrLUVK.mjs → client-gpRgZl1b.mjs} +5 -4
  20. package/dist/configure/index.d.mts +6 -5
  21. package/dist/configure/index.mjs +85 -14
  22. package/dist/configure/index.mjs.map +1 -1
  23. package/dist/crash-report-Cm9vFmTN.mjs +7 -0
  24. package/dist/{crash-report-wNxS0IUJ.mjs → crash-report-DnwITWeV.mjs} +5 -4
  25. package/dist/{crash-report-wNxS0IUJ.mjs.map → crash-report-DnwITWeV.mjs.map} +1 -1
  26. package/dist/{enum-constants-D1nfn0qD.mjs → enum-constants-Piv_E-2M.mjs} +3 -1
  27. package/dist/{enum-constants-D1nfn0qD.mjs.map → enum-constants-Piv_E-2M.mjs.map} +1 -1
  28. package/dist/{env-BgdHfWDn.d.mts → env-CSZ9CKg7.d.mts} +3 -2
  29. package/dist/{file-utils-Bctuzn3x.mjs → file-utils-B7xME5IK.mjs} +3 -1
  30. package/dist/{file-utils-Bctuzn3x.mjs.map → file-utils-B7xME5IK.mjs.map} +1 -1
  31. package/dist/{index-BAud4CE7.d.mts → index-BdlrrjvD.d.mts} +145 -35
  32. package/dist/{index-D4Y81vh1.d.mts → index-Dlpe_4Nd.d.mts} +4 -2
  33. package/dist/{index-FZMBoUWm.d.mts → index-IHl7P_9I.d.mts} +4 -2
  34. package/dist/{index-CxgBnxKM.d.mts → index-dg3Sf-No.d.mts} +4 -2
  35. package/dist/{index-CdDzh-T2.d.mts → index-uNv9YJgx.d.mts} +4 -2
  36. package/dist/{interceptor-DgQNmwWJ.mjs → interceptor-1LL1gBsF.mjs} +3 -2
  37. package/dist/{interceptor-DgQNmwWJ.mjs.map → interceptor-1LL1gBsF.mjs.map} +1 -1
  38. package/dist/{job-DdfW7vH3.mjs → job-CPKYCk_e.mjs} +3 -2
  39. package/dist/{job-DdfW7vH3.mjs.map → job-CPKYCk_e.mjs.map} +1 -1
  40. package/dist/kysely/index.d.mts +1 -0
  41. package/dist/kysely/index.mjs +2 -1
  42. package/dist/kysely/index.mjs.map +1 -1
  43. package/dist/{kysely-type-B_IecdK9.mjs → kysely-type-t5MbP7iJ.mjs} +3 -1
  44. package/dist/{kysely-type-B_IecdK9.mjs.map → kysely-type-t5MbP7iJ.mjs.map} +1 -1
  45. package/dist/{logger-CqezTedh.mjs → logger-qz-Y4sBV.mjs} +2 -1
  46. package/dist/{logger-CqezTedh.mjs.map → logger-qz-Y4sBV.mjs.map} +1 -1
  47. package/dist/package-json-88x7bPKC.mjs +5 -0
  48. package/dist/{package-json-D3x2nBPB.mjs → package-json-VqyFvGiP.mjs} +2 -1
  49. package/dist/{package-json-D3x2nBPB.mjs.map → package-json-VqyFvGiP.mjs.map} +1 -1
  50. package/dist/plugin/builtin/enum-constants/index.d.mts +2 -1
  51. package/dist/plugin/builtin/enum-constants/index.mjs +3 -2
  52. package/dist/plugin/builtin/file-utils/index.d.mts +2 -1
  53. package/dist/plugin/builtin/file-utils/index.mjs +3 -2
  54. package/dist/plugin/builtin/kysely-type/index.d.mts +2 -1
  55. package/dist/plugin/builtin/kysely-type/index.mjs +3 -2
  56. package/dist/plugin/builtin/seed/index.d.mts +2 -1
  57. package/dist/plugin/builtin/seed/index.mjs +3 -2
  58. package/dist/plugin/index.d.mts +3 -2
  59. package/dist/plugin/index.mjs +2 -1
  60. package/dist/plugin/index.mjs.map +1 -1
  61. package/dist/{plugin-DyVYXZWz.d.mts → plugin-BC7WQrjm.d.mts} +25 -13
  62. package/dist/{runtime-DbX_UxlC.mjs → runtime-3P9JFpe9.mjs} +1036 -321
  63. package/dist/runtime-3P9JFpe9.mjs.map +1 -0
  64. package/dist/{schema-CNWt2FKQ.mjs → schema-D27cW0Ca.mjs} +4 -2
  65. package/dist/{schema-CNWt2FKQ.mjs.map → schema-D27cW0Ca.mjs.map} +1 -1
  66. package/dist/seed/index.d.mts +1 -0
  67. package/dist/seed/index.mjs +2 -1
  68. package/dist/seed/index.mjs.map +1 -1
  69. package/dist/{seed-CWkIDWMb.mjs → seed-DtYgudLq.mjs} +12 -3
  70. package/dist/seed-DtYgudLq.mjs.map +1 -0
  71. package/dist/{telemetry-BSUlYTs-.mjs → telemetry-B4sp-dD2.mjs} +3 -2
  72. package/dist/{telemetry-BSUlYTs-.mjs.map → telemetry-B4sp-dD2.mjs.map} +1 -1
  73. package/dist/telemetry-DXIGGa6A.mjs +5 -0
  74. package/dist/utils/test/index.d.mts +3 -2
  75. package/dist/utils/test/index.mjs +4 -3
  76. package/dist/utils/test/index.mjs.map +1 -1
  77. package/dist/{workflow.generated-DMQ-cjyT.d.mts → workflow.generated-Cd5dsFnf.d.mts} +3 -2
  78. package/docs/cli/application.md +17 -0
  79. package/docs/generator/builtin.md +0 -8
  80. package/docs/services/executor.md +54 -0
  81. package/package.json +18 -17
  82. package/dist/application-B8vok3w2.mjs.map +0 -1
  83. package/dist/application-DHifc-oA.mjs +0 -11
  84. package/dist/crash-report-BMEhRxeg.mjs +0 -6
  85. package/dist/package-json-DiZWrkIA.mjs +0 -4
  86. package/dist/runtime-DbX_UxlC.mjs.map +0 -1
  87. package/dist/seed-CWkIDWMb.mjs.map +0 -1
  88. package/dist/telemetry-BtN2l0f1.mjs +0 -4
@@ -1,9 +1,10 @@
1
- import { t as db } from "./schema-CNWt2FKQ.mjs";
2
- import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-CqezTedh.mjs";
3
- import { A as ExecutorTriggerType, C as TailorDBType_PermitAction, E as FunctionExecution_Status, F as AuthOAuth2Client_GrantType, G as Condition_Operator, H as UserProfileProviderConfig_UserProfileProviderType, I as AuthSCIMAttribute_Mutability, J as ApplicationSchemaUpdateAttemptStatus, K as FilterSchema, L as AuthSCIMAttribute_Type, M as AuthIDPConfig_AuthType, N as AuthInvokerSchema, O as ExecutorJobStatus, P as AuthOAuth2Client_ClientType, R as AuthSCIMAttribute_Uniqueness, S as TailorDBType_Permission_Permit, T as IdPLang, U as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, V as TenantProviderConfig_TenantProviderType, W as ConditionSchema, Y as Subgraph_ServiceType, _ as WorkflowJobExecution_Status, a as fetchMachineUserToken, b as TailorDBGQLPermission_Permit, f as platformBaseUrl, g as WorkflowExecution_Status, h as WorkspacePlatformUserRole, i as fetchAll, j as AuthHookPoint, k as ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as PageDirection, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMConfig_AuthorizationType } from "./client-DfdgRZlQ.mjs";
4
- import { t as readPackageJson } from "./package-json-D3x2nBPB.mjs";
5
- import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, l as OAuth2ClientSchema, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, s as createExecutorService, t as defineApplication, x as loadWorkspaceId } from "./application-B8vok3w2.mjs";
6
- import { r as withSpan } from "./telemetry-BSUlYTs-.mjs";
1
+
2
+ import { t as db } from "./schema-D27cW0Ca.mjs";
3
+ import { i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-qz-Y4sBV.mjs";
4
+ import { A as ExecutorTriggerType, C as TailorDBType_PermitAction, E as FunctionExecution_Status, F as AuthOAuth2Client_GrantType, G as Condition_Operator, H as UserProfileProviderConfig_UserProfileProviderType, I as AuthSCIMAttribute_Mutability, J as ApplicationSchemaUpdateAttemptStatus, K as FilterSchema, L as AuthSCIMAttribute_Type, M as AuthIDPConfig_AuthType, N as AuthInvokerSchema, O as ExecutorJobStatus, P as AuthOAuth2Client_ClientType, R as AuthSCIMAttribute_Uniqueness, S as TailorDBType_Permission_Permit, T as IdPLang, U as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, V as TenantProviderConfig_TenantProviderType, W as ConditionSchema, Y as Subgraph_ServiceType, _ as WorkflowJobExecution_Status, a as fetchMachineUserToken, b as TailorDBGQLPermission_Permit, f as platformBaseUrl, g as WorkflowExecution_Status, h as WorkspacePlatformUserRole, i as fetchAll, j as AuthHookPoint, k as ExecutorTargetType, m as userAgent, p as resolveStaticWebsiteUrls, q as PageDirection, u as initOperatorClient, v as TailorDBGQLPermission_Action, w as PipelineResolver_OperationType, x as TailorDBType_Permission_Operator, y as TailorDBGQLPermission_Operator, z as AuthSCIMConfig_AuthorizationType } from "./client-D5P1myz0.mjs";
5
+ import { t as readPackageJson } from "./package-json-VqyFvGiP.mjs";
6
+ import { S as readPlatformConfig, T as writePlatformConfig, _ as hashFile, a as loadConfig, b as loadAccessToken, d as TailorDBTypeSchema, f as stringifyFunction, g as getDistDir, h as createBundleCache, l as OAuth2ClientSchema, m as loadFilesWithIgnores, n as generatePluginFilesIfNeeded, p as tailorUserMap, r as loadApplication, s as createExecutorService, t as defineApplication, x as loadWorkspaceId } from "./application-CfAom-vi.mjs";
7
+ import { r as withSpan } from "./telemetry-B4sp-dD2.mjs";
7
8
  import { arg, createDefineCommand, defineCommand, runCommand } from "politty";
8
9
  import { z } from "zod";
9
10
  import * as fs$1 from "node:fs";
@@ -890,6 +891,7 @@ function createChangeSet(title) {
890
891
  const updates = [];
891
892
  const deletes = [];
892
893
  const replaces = [];
894
+ const unchanged = [];
893
895
  const isEmpty = () => creates.length === 0 && updates.length === 0 && deletes.length === 0 && replaces.length === 0;
894
896
  return {
895
897
  title,
@@ -897,6 +899,7 @@ function createChangeSet(title) {
897
899
  updates,
898
900
  deletes,
899
901
  replaces,
902
+ unchanged,
900
903
  isEmpty,
901
904
  print: () => {
902
905
  if (isEmpty()) return;
@@ -908,6 +911,83 @@ function createChangeSet(title) {
908
911
  }
909
912
  };
910
913
  }
914
+ /**
915
+ * Summarize resource counts across multiple change sets.
916
+ * @param changeSets - Change sets to aggregate
917
+ * @returns Aggregated plan counts by action
918
+ */
919
+ function summarizeChangeSets(changeSets) {
920
+ const summary = {
921
+ create: 0,
922
+ update: 0,
923
+ delete: 0,
924
+ replace: 0,
925
+ unchanged: 0
926
+ };
927
+ for (const changeSet of changeSets) {
928
+ summary.create += changeSet.creates.length;
929
+ summary.update += changeSet.updates.length;
930
+ summary.delete += changeSet.deletes.length;
931
+ summary.replace += changeSet.replaces.length;
932
+ summary.unchanged += changeSet.unchanged.length;
933
+ }
934
+ return summary;
935
+ }
936
+ /**
937
+ * Format an aggregated plan summary for CLI output.
938
+ * @param summary - Aggregated plan counts
939
+ * @returns Human-readable plan summary line
940
+ */
941
+ function formatPlanSummary(summary) {
942
+ const parts = [
943
+ `${summary.create} to create`,
944
+ `${summary.update} to update`,
945
+ `${summary.delete} to delete`
946
+ ];
947
+ if (summary.replace > 0) parts.push(`${summary.replace} to replace`);
948
+ parts.push(`${summary.unchanged} unchanged`);
949
+ return `Plan: ${parts.join(", ")}`;
950
+ }
951
+
952
+ //#endregion
953
+ //#region src/cli/commands/apply/compare.ts
954
+ /**
955
+ * Stable JSON-like serialization that sorts object keys and ignores proto runtime metadata.
956
+ * @param value - Value to serialize
957
+ * @returns Stable serialized string
958
+ */
959
+ function stableStringify(value) {
960
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : stableStringify(item)).join(",")}]`;
961
+ if (value && typeof value === "object") return `{${Object.entries(value).filter(([key, entryValue]) => key !== "$typeName" && entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`;
962
+ if (typeof value === "bigint") return JSON.stringify(value.toString());
963
+ return JSON.stringify(value);
964
+ }
965
+ /**
966
+ * Normalize a proto-ish object into a plain JSON-compatible structure for comparison.
967
+ * @param value - Value to normalize
968
+ * @returns Normalized value
969
+ */
970
+ function normalizeProtoConfig(value) {
971
+ if (value === void 0 || value === null) return value;
972
+ return JSON.parse(stableStringify(value));
973
+ }
974
+ /**
975
+ * Sort a string array for order-insensitive comparison.
976
+ * @param values - Values to sort
977
+ * @returns Sorted values
978
+ */
979
+ function normalizeStringArray(values) {
980
+ return [...values ?? []].sort();
981
+ }
982
+ /**
983
+ * Compare two values after proto normalization.
984
+ * @param left - Left value
985
+ * @param right - Right value
986
+ * @returns True when normalized values are equal
987
+ */
988
+ function areNormalizedEqual(left, right) {
989
+ return stableStringify(normalizeProtoConfig(left)) === stableStringify(normalizeProtoConfig(right));
990
+ }
911
991
 
912
992
  //#endregion
913
993
  //#region src/cli/commands/apply/label.ts
@@ -920,6 +1000,16 @@ function trnPrefix(workspaceId) {
920
1000
  return `trn:v1:workspace:${workspaceId}`;
921
1001
  }
922
1002
  const sdkNameLabelKey = "sdk-name";
1003
+ const sdkVersionLabelKey = "sdk-version";
1004
+ /**
1005
+ * Check whether existing metadata was produced by the current SDK version.
1006
+ * @param existingLabels - Labels currently stored on the remote resource
1007
+ * @param desiredLabels - Labels that will be written by the current apply run
1008
+ * @returns True when sdk-version matches
1009
+ */
1010
+ function hasMatchingSdkVersion(existingLabels, desiredLabels) {
1011
+ return existingLabels?.[sdkVersionLabelKey] === desiredLabels?.[sdkVersionLabelKey];
1012
+ }
923
1013
  /**
924
1014
  * Build metadata request with SDK labels.
925
1015
  * @param trn - Target TRN
@@ -935,7 +1025,7 @@ async function buildMetaRequest(trn, appName, existingLabels) {
935
1025
  labels: {
936
1026
  ...existingLabels ?? {},
937
1027
  [sdkNameLabelKey]: appName,
938
- "sdk-version": sdkVersion
1028
+ [sdkVersionLabelKey]: sdkVersion
939
1029
  }
940
1030
  };
941
1031
  }
@@ -966,6 +1056,54 @@ async function applyApplication(client, changeSet, phase = "create-update") {
966
1056
  function trn$6(workspaceId, name) {
967
1057
  return `trn:v1:workspace:${workspaceId}:application:${name}`;
968
1058
  }
1059
+ function sortStrings(values) {
1060
+ return [...values ?? []].sort();
1061
+ }
1062
+ function normalizeSubgraphs(subgraphs) {
1063
+ return [...subgraphs ?? []].map((subgraph) => ({
1064
+ serviceType: subgraph.serviceType,
1065
+ serviceNamespace: subgraph.serviceNamespace ?? ""
1066
+ })).sort((left, right) => {
1067
+ if (left.serviceType !== right.serviceType) return left.serviceType - right.serviceType;
1068
+ return left.serviceNamespace.localeCompare(right.serviceNamespace);
1069
+ });
1070
+ }
1071
+ function toComparableApplication(input) {
1072
+ return {
1073
+ authNamespace: input.authNamespace,
1074
+ authIdpConfigName: input.authIdpConfigName,
1075
+ cors: sortStrings(input.cors),
1076
+ subgraphs: [...input.subgraphs],
1077
+ allowedIpAddresses: sortStrings(input.allowedIpAddresses),
1078
+ disableIntrospection: input.disableIntrospection,
1079
+ disabled: input.disabled
1080
+ };
1081
+ }
1082
+ function normalizeComparableApplication(application, authNamespace, authIdpConfigName, cors) {
1083
+ return toComparableApplication({
1084
+ authNamespace: authNamespace ?? "",
1085
+ authIdpConfigName: authIdpConfigName ?? "",
1086
+ cors,
1087
+ subgraphs: normalizeSubgraphs(application.subgraphs.map((subgraph) => protoSubgraph(subgraph))),
1088
+ allowedIpAddresses: application.config.allowedIpAddresses ?? [],
1089
+ disableIntrospection: application.config.disableIntrospection ?? false,
1090
+ disabled: false
1091
+ });
1092
+ }
1093
+ function normalizeComparableExistingApplication(app) {
1094
+ return toComparableApplication({
1095
+ authNamespace: app.authNamespace,
1096
+ authIdpConfigName: app.authIdpConfigName,
1097
+ cors: app.cors,
1098
+ subgraphs: normalizeSubgraphs(app.subgraphs),
1099
+ allowedIpAddresses: app.allowedIpAddresses,
1100
+ disableIntrospection: app.disableIntrospection,
1101
+ disabled: app.disabled
1102
+ });
1103
+ }
1104
+ function areApplicationsEqual(existing, desired) {
1105
+ return areNormalizedEqual(normalizeComparableExistingApplication(existing), desired);
1106
+ }
969
1107
  /**
970
1108
  * Plan application changes based on current and desired state.
971
1109
  * @param context - Planning context
@@ -1027,32 +1165,30 @@ async function planApplication(context) {
1027
1165
  if (idpConfigs.length > 0) authIdpConfigName = idpConfigs[0].name;
1028
1166
  }
1029
1167
  const metaRequest = await buildMetaRequest(trn$6(workspaceId, application.name), application.name);
1030
- if (existingApplications.some((app) => app.name === application.name)) changeSet.updates.push({
1031
- name: application.name,
1032
- request: {
1033
- workspaceId,
1034
- applicationName: application.name,
1035
- authNamespace,
1036
- authIdpConfigName,
1037
- cors: application.config.cors,
1038
- subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1039
- allowedIpAddresses: application.config.allowedIpAddresses,
1040
- disableIntrospection: application.config.disableIntrospection
1041
- },
1042
- metaRequest
1043
- });
1044
- else changeSet.creates.push({
1168
+ const resolvedCors = await resolveStaticWebsiteUrls(client, workspaceId, application.config.cors, "CORS");
1169
+ const desired = normalizeComparableApplication(application, authNamespace, authIdpConfigName, resolvedCors);
1170
+ const request = {
1171
+ workspaceId,
1172
+ applicationName: application.name,
1173
+ authNamespace,
1174
+ authIdpConfigName,
1175
+ cors: application.config.cors,
1176
+ subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1177
+ allowedIpAddresses: application.config.allowedIpAddresses,
1178
+ disableIntrospection: application.config.disableIntrospection
1179
+ };
1180
+ const existing = existingApplications.find((app) => app.name === application.name);
1181
+ if (existing) {
1182
+ const { metadata } = await client.getMetadata({ trn: trn$6(workspaceId, application.name) });
1183
+ if (metadata?.labels?.["sdk-name"] === application.name && hasMatchingSdkVersion(metadata?.labels, metaRequest.labels) && areApplicationsEqual(existing, desired)) changeSet.unchanged.push({ name: application.name });
1184
+ else changeSet.updates.push({
1185
+ name: application.name,
1186
+ request,
1187
+ metaRequest
1188
+ });
1189
+ } else changeSet.creates.push({
1045
1190
  name: application.name,
1046
- request: {
1047
- workspaceId,
1048
- applicationName: application.name,
1049
- authNamespace,
1050
- authIdpConfigName,
1051
- cors: application.config.cors,
1052
- subgraphs: application.subgraphs.map((subgraph) => protoSubgraph(subgraph)),
1053
- allowedIpAddresses: application.config.allowedIpAddresses,
1054
- disableIntrospection: application.config.disableIntrospection
1055
- },
1191
+ request,
1056
1192
  metaRequest
1057
1193
  });
1058
1194
  changeSet.print();
@@ -1092,7 +1228,7 @@ const CHUNK_SIZE = 64 * 1024;
1092
1228
  function computeContentHash(content) {
1093
1229
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1094
1230
  }
1095
- function functionRegistryTrn(workspaceId, name) {
1231
+ function functionRegistryTrn$1(workspaceId, name) {
1096
1232
  return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
1097
1233
  }
1098
1234
  /**
@@ -1198,6 +1334,16 @@ function collectFunctionEntries(application, workflowJobs, bundledScripts) {
1198
1334
  return entries;
1199
1335
  }
1200
1336
  /**
1337
+ * Filter collected workflow jobs down to the ones actually bundled.
1338
+ * @param jobs - All collected workflow jobs
1339
+ * @param usedJobNames - Job names that were bundled
1340
+ * @returns Bundled workflow jobs only
1341
+ */
1342
+ function filterBundledWorkflowJobs(jobs, usedJobNames) {
1343
+ const used = new Set(usedJobNames);
1344
+ return jobs.filter((job) => used.has(job.name));
1345
+ }
1346
+ /**
1201
1347
  * Plan function registry changes based on current and desired state.
1202
1348
  * @param client - Operator client instance
1203
1349
  * @param workspaceId - Workspace ID
@@ -1228,16 +1374,18 @@ async function planFunctionRegistry(client, workspaceId, appName, entries) {
1228
1374
  });
1229
1375
  const existingMap = {};
1230
1376
  await Promise.all(existingFunctions.map(async (func) => {
1231
- const { metadata } = await client.getMetadata({ trn: functionRegistryTrn(workspaceId, func.name) });
1377
+ const { metadata } = await client.getMetadata({ trn: functionRegistryTrn$1(workspaceId, func.name) });
1232
1378
  existingMap[func.name] = {
1233
1379
  resource: func,
1234
- label: metadata?.labels[sdkNameLabelKey]
1380
+ label: metadata?.labels[sdkNameLabelKey],
1381
+ allLabels: metadata?.labels
1235
1382
  };
1236
1383
  }));
1237
1384
  for (const entry of entries) {
1238
1385
  const existing = existingMap[entry.name];
1239
- const metaRequest = await buildMetaRequest(functionRegistryTrn(workspaceId, entry.name), appName);
1386
+ const metaRequest = await buildMetaRequest(functionRegistryTrn$1(workspaceId, entry.name), appName);
1240
1387
  if (existing) {
1388
+ const isManagedByApp = existing.label === appName;
1241
1389
  if (!existing.label) unmanaged.push({
1242
1390
  resourceType: "Function registry",
1243
1391
  resourceName: entry.name
@@ -1247,7 +1395,8 @@ async function planFunctionRegistry(client, workspaceId, appName, entries) {
1247
1395
  resourceName: entry.name,
1248
1396
  currentOwner: existing.label
1249
1397
  });
1250
- changeSet.updates.push({
1398
+ if (existing.resource.contentHash === entry.contentHash && isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: entry.name });
1399
+ else changeSet.updates.push({
1251
1400
  name: entry.name,
1252
1401
  entry,
1253
1402
  metaRequest
@@ -1434,10 +1583,10 @@ async function applyIdP(client, result, phase = "create-update") {
1434
1583
  * @returns Planned changes and metadata
1435
1584
  */
1436
1585
  async function planIdP(context) {
1437
- const { client, workspaceId, application, forRemoval } = context;
1586
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1438
1587
  const idps = forRemoval ? [] : application.idpServices;
1439
1588
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, idps);
1440
- const clientChangeSet = await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name));
1589
+ const clientChangeSet = await planClients(client, workspaceId, idps, serviceChangeSet.deletes.map((del) => del.name), forceApplyAll);
1441
1590
  serviceChangeSet.print();
1442
1591
  clientChangeSet.print();
1443
1592
  return {
@@ -1453,6 +1602,49 @@ async function planIdP(context) {
1453
1602
  function trn$5(workspaceId, name) {
1454
1603
  return `trn:v1:workspace:${workspaceId}:idp:${name}`;
1455
1604
  }
1605
+ function normalizeComparableUserAuthPolicy(policy) {
1606
+ return {
1607
+ useNonEmailIdentifier: policy?.useNonEmailIdentifier ?? false,
1608
+ allowSelfPasswordReset: policy?.allowSelfPasswordReset ?? false,
1609
+ passwordRequireUppercase: policy?.passwordRequireUppercase ?? false,
1610
+ passwordRequireLowercase: policy?.passwordRequireLowercase ?? false,
1611
+ passwordRequireNonAlphanumeric: policy?.passwordRequireNonAlphanumeric ?? false,
1612
+ passwordRequireNumeric: policy?.passwordRequireNumeric ?? false,
1613
+ passwordMinLength: policy?.passwordMinLength ?? 0,
1614
+ passwordMaxLength: policy?.passwordMaxLength ?? 0,
1615
+ allowedEmailDomains: [...policy?.allowedEmailDomains ?? []].sort(),
1616
+ allowGoogleOauth: policy?.allowGoogleOauth ?? false,
1617
+ disablePasswordAuth: policy?.disablePasswordAuth ?? false,
1618
+ allowMicrosoftOauth: policy?.allowMicrosoftOauth ?? false
1619
+ };
1620
+ }
1621
+ function normalizeComparableDisableGqlOperations(value) {
1622
+ return {
1623
+ create: value?.create ?? false,
1624
+ update: value?.update ?? false,
1625
+ delete: value?.delete ?? false,
1626
+ read: value?.read ?? false,
1627
+ sendPasswordResetEmail: value?.sendPasswordResetEmail ?? false
1628
+ };
1629
+ }
1630
+ function normalizeComparableIdPService(input) {
1631
+ return {
1632
+ authorization: input.authorization,
1633
+ lang: input.lang === IdPLang.UNSPECIFIED ? IdPLang.EN : input.lang,
1634
+ userAuthPolicy: input.userAuthPolicy,
1635
+ publishUserEvents: input.publishUserEvents,
1636
+ disableGqlOperations: input.disableGqlOperations
1637
+ };
1638
+ }
1639
+ function areIdPServicesEqual(existing, desired) {
1640
+ return areNormalizedEqual(normalizeComparableIdPService({
1641
+ authorization: existing.authorization,
1642
+ lang: existing.lang,
1643
+ userAuthPolicy: normalizeComparableUserAuthPolicy(existing.userAuthPolicy),
1644
+ publishUserEvents: existing.publishUserEvents,
1645
+ disableGqlOperations: normalizeComparableDisableGqlOperations(existing.disableGqlOperations)
1646
+ }), desired);
1647
+ }
1456
1648
  async function planServices$3(client, workspaceId, appName, idps) {
1457
1649
  const changeSet = createChangeSet("IdP services");
1458
1650
  const conflicts = [];
@@ -1477,7 +1669,8 @@ async function planServices$3(client, workspaceId, appName, idps) {
1477
1669
  const { metadata } = await client.getMetadata({ trn: trn$5(workspaceId, resource.namespace.name) });
1478
1670
  existingServices[resource.namespace.name] = {
1479
1671
  resource,
1480
- label: metadata?.labels[sdkNameLabelKey]
1672
+ label: metadata?.labels[sdkNameLabelKey],
1673
+ allLabels: metadata?.labels
1481
1674
  };
1482
1675
  }));
1483
1676
  for (const idp of idps) {
@@ -1498,7 +1691,25 @@ async function planServices$3(client, workspaceId, appName, idps) {
1498
1691
  }
1499
1692
  const lang = convertLang(idp.lang);
1500
1693
  const userAuthPolicy = idp.userAuthPolicy;
1694
+ const publishUserEvents = idp.publishUserEvents ?? false;
1695
+ const desired = normalizeComparableIdPService({
1696
+ authorization,
1697
+ lang,
1698
+ userAuthPolicy: normalizeComparableUserAuthPolicy(userAuthPolicy),
1699
+ publishUserEvents,
1700
+ disableGqlOperations: normalizeComparableDisableGqlOperations(convertGqlOperationsToDisable(idp.gqlOperations))
1701
+ });
1702
+ const request = {
1703
+ workspaceId,
1704
+ namespaceName,
1705
+ authorization,
1706
+ lang,
1707
+ userAuthPolicy,
1708
+ publishUserEvents,
1709
+ disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1710
+ };
1501
1711
  if (existing) {
1712
+ const isManagedByApp = existing.label === appName;
1502
1713
  if (!existing.label) unmanaged.push({
1503
1714
  resourceType: "IdP service",
1504
1715
  resourceName: idp.name
@@ -1508,31 +1719,16 @@ async function planServices$3(client, workspaceId, appName, idps) {
1508
1719
  resourceName: idp.name,
1509
1720
  currentOwner: existing.label
1510
1721
  });
1511
- changeSet.updates.push({
1722
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
1723
+ else changeSet.updates.push({
1512
1724
  name: namespaceName,
1513
- request: {
1514
- workspaceId,
1515
- namespaceName,
1516
- authorization,
1517
- lang,
1518
- userAuthPolicy,
1519
- publishUserEvents: idp.publishUserEvents,
1520
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1521
- },
1725
+ request,
1522
1726
  metaRequest
1523
1727
  });
1524
1728
  delete existingServices[namespaceName];
1525
1729
  } else changeSet.creates.push({
1526
1730
  name: namespaceName,
1527
- request: {
1528
- workspaceId,
1529
- namespaceName,
1530
- authorization,
1531
- lang,
1532
- userAuthPolicy,
1533
- publishUserEvents: idp.publishUserEvents,
1534
- disableGqlOperations: convertGqlOperationsToDisable(idp.gqlOperations)
1535
- },
1731
+ request,
1536
1732
  metaRequest
1537
1733
  });
1538
1734
  }
@@ -1554,7 +1750,7 @@ async function planServices$3(client, workspaceId, appName, idps) {
1554
1750
  resourceOwners
1555
1751
  };
1556
1752
  }
1557
- async function planClients(client, workspaceId, idps, deletedServices) {
1753
+ async function planClients(client, workspaceId, idps, deletedServices, forceApplyAll = false) {
1558
1754
  const changeSet = createChangeSet("IdP clients");
1559
1755
  const fetchClients = (namespaceName) => {
1560
1756
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1582,12 +1778,13 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1582
1778
  existingNameMap.set(client.name, client.clientSecret);
1583
1779
  });
1584
1780
  for (const name of idp.clients) if (existingNameMap.has(name)) {
1585
- changeSet.updates.push({
1781
+ if (forceApplyAll) changeSet.updates.push({
1586
1782
  name,
1587
1783
  workspaceId,
1588
1784
  namespaceName,
1589
- clientSecret: existingNameMap.get(name)
1785
+ clientSecret: existingNameMap.get(name) ?? ""
1590
1786
  });
1787
+ else changeSet.unchanged.push({ name });
1591
1788
  existingNameMap.delete(name);
1592
1789
  } else changeSet.creates.push({
1593
1790
  name,
@@ -1597,7 +1794,7 @@ async function planClients(client, workspaceId, idps, deletedServices) {
1597
1794
  client: { name }
1598
1795
  }
1599
1796
  });
1600
- existingNameMap.forEach((name) => {
1797
+ existingNameMap.forEach((_clientSecret, name) => {
1601
1798
  changeSet.deletes.push({
1602
1799
  name,
1603
1800
  request: {
@@ -1703,21 +1900,21 @@ async function applyAuth(client, result, phase = "create-update") {
1703
1900
  * @returns Planned auth changes and metadata
1704
1901
  */
1705
1902
  async function planAuth(context) {
1706
- const { client, workspaceId, application, forRemoval } = context;
1903
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
1707
1904
  const auths = [];
1708
1905
  if (!forRemoval && application.authService) {
1709
1906
  await application.authService.resolveNamespaces();
1710
1907
  auths.push(application.authService);
1711
1908
  }
1712
- const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths);
1909
+ const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, auths, forceApplyAll);
1713
1910
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
1714
1911
  const [idpConfigChangeSet, userProfileConfigChangeSet, tenantConfigChangeSet, machineUserChangeSet, authHookChangeSet, oauth2ClientChangeSet, scimChangeSet, scimResourceChangeSet] = await Promise.all([
1715
- planIdPConfigs(client, workspaceId, auths, deletedServices),
1716
- planUserProfileConfigs(client, workspaceId, auths, deletedServices),
1717
- planTenantConfigs(client, workspaceId, auths, deletedServices),
1718
- planMachineUsers(client, workspaceId, auths, deletedServices),
1719
- planAuthHooks(client, workspaceId, auths, deletedServices),
1720
- planOAuth2Clients(client, workspaceId, auths, deletedServices),
1912
+ planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1913
+ planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1914
+ planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
1915
+ planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll),
1916
+ planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll),
1917
+ planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll),
1721
1918
  planSCIMConfigs(client, workspaceId, auths, deletedServices),
1722
1919
  planSCIMResources(client, workspaceId, auths, deletedServices)
1723
1920
  ]);
@@ -1750,7 +1947,7 @@ async function planAuth(context) {
1750
1947
  function trn$4(workspaceId, name) {
1751
1948
  return `trn:v1:workspace:${workspaceId}:auth:${name}`;
1752
1949
  }
1753
- async function planServices$2(client, workspaceId, appName, auths) {
1950
+ async function planServices$2(client, workspaceId, appName, auths, forceApplyAll = false) {
1754
1951
  const changeSet = createChangeSet("Auth services");
1755
1952
  const conflicts = [];
1756
1953
  const unmanaged = [];
@@ -1781,7 +1978,13 @@ async function planServices$2(client, workspaceId, appName, auths) {
1781
1978
  const { parsedConfig: config } = auth;
1782
1979
  const existing = existingServices[config.name];
1783
1980
  const metaRequest = await buildMetaRequest(trn$4(workspaceId, config.name), appName);
1981
+ const request = {
1982
+ workspaceId,
1983
+ namespaceName: config.name,
1984
+ publishSessionEvents: config.publishSessionEvents
1985
+ };
1784
1986
  if (existing) {
1987
+ const isManagedByApp = existing.label === appName;
1785
1988
  if (!existing.label) unmanaged.push({
1786
1989
  resourceType: "Auth service",
1787
1990
  resourceName: config.name
@@ -1791,23 +1994,16 @@ async function planServices$2(client, workspaceId, appName, auths) {
1791
1994
  resourceName: config.name,
1792
1995
  currentOwner: existing.label
1793
1996
  });
1794
- changeSet.updates.push({
1997
+ if (!forceApplyAll && existing.resource.publishSessionEvents === (config.publishSessionEvents ?? false) && isManagedByApp) changeSet.unchanged.push({ name: config.name });
1998
+ else changeSet.updates.push({
1795
1999
  name: config.name,
1796
- request: {
1797
- workspaceId,
1798
- namespaceName: config.name,
1799
- publishSessionEvents: config.publishSessionEvents
1800
- },
2000
+ request,
1801
2001
  metaRequest
1802
2002
  });
1803
2003
  delete existingServices[config.name];
1804
2004
  } else changeSet.creates.push({
1805
2005
  name: config.name,
1806
- request: {
1807
- workspaceId,
1808
- namespaceName: config.name,
1809
- publishSessionEvents: config.publishSessionEvents
1810
- },
2006
+ request,
1811
2007
  metaRequest
1812
2008
  });
1813
2009
  }
@@ -1829,7 +2025,7 @@ async function planServices$2(client, workspaceId, appName, auths) {
1829
2025
  resourceOwners
1830
2026
  };
1831
2027
  }
1832
- async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
2028
+ async function planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1833
2029
  const changeSet = createChangeSet("Auth idpConfigs");
1834
2030
  const fetchIdPConfigs = (namespaceName) => {
1835
2031
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -1850,32 +2046,51 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1850
2046
  for (const authService of auths) {
1851
2047
  const { parsedConfig: config } = authService;
1852
2048
  const existingIdPConfigs = await fetchIdPConfigs(config.name);
1853
- const existingNameSet = /* @__PURE__ */ new Set();
2049
+ const existingMap = /* @__PURE__ */ new Map();
1854
2050
  existingIdPConfigs.forEach((idpConfig) => {
1855
- existingNameSet.add(idpConfig.name);
2051
+ existingMap.set(idpConfig.name, idpConfig);
1856
2052
  });
1857
2053
  const idpConfig = config.idProvider;
1858
- if (idpConfig) if (existingNameSet.has(idpConfig.name)) {
1859
- changeSet.updates.push({
2054
+ if (idpConfig) {
2055
+ const desired = protoIdPConfig(idpConfig);
2056
+ const existing = existingMap.get(idpConfig.name);
2057
+ if (existing) {
2058
+ const desiredComparable = await protoIdPConfigForComparison(client, workspaceId, idpConfig, desired);
2059
+ if (!desiredComparable) {
2060
+ changeSet.updates.push({
2061
+ name: idpConfig.name,
2062
+ idpConfig,
2063
+ request: {
2064
+ workspaceId,
2065
+ namespaceName: config.name,
2066
+ idpConfig: desired
2067
+ }
2068
+ });
2069
+ existingMap.delete(idpConfig.name);
2070
+ continue;
2071
+ }
2072
+ if (!forceApplyAll && areAuthIdPConfigsEqual(existing, desiredComparable)) changeSet.unchanged.push({ name: idpConfig.name });
2073
+ else changeSet.updates.push({
2074
+ name: idpConfig.name,
2075
+ idpConfig,
2076
+ request: {
2077
+ workspaceId,
2078
+ namespaceName: config.name,
2079
+ idpConfig: desired
2080
+ }
2081
+ });
2082
+ existingMap.delete(idpConfig.name);
2083
+ } else changeSet.creates.push({
1860
2084
  name: idpConfig.name,
1861
2085
  idpConfig,
1862
2086
  request: {
1863
2087
  workspaceId,
1864
2088
  namespaceName: config.name,
1865
- idpConfig: protoIdPConfig(idpConfig)
2089
+ idpConfig: desired
1866
2090
  }
1867
2091
  });
1868
- existingNameSet.delete(idpConfig.name);
1869
- } else changeSet.creates.push({
1870
- name: idpConfig.name,
1871
- idpConfig,
1872
- request: {
1873
- workspaceId,
1874
- namespaceName: config.name,
1875
- idpConfig: protoIdPConfig(idpConfig)
1876
- }
1877
- });
1878
- existingNameSet.forEach((name) => {
2092
+ }
2093
+ existingMap.forEach((_, name) => {
1879
2094
  changeSet.deletes.push({
1880
2095
  name,
1881
2096
  request: {
@@ -1898,6 +2113,32 @@ async function planIdPConfigs(client, workspaceId, auths, deletedServices) {
1898
2113
  });
1899
2114
  return changeSet;
1900
2115
  }
2116
+ async function protoIdPConfigForComparison(client, workspaceId, idpConfig, desired) {
2117
+ if (idpConfig.kind !== "BuiltInIdP") return desired;
2118
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, idpConfig);
2119
+ return config ? {
2120
+ ...desired,
2121
+ config
2122
+ } : void 0;
2123
+ }
2124
+ function normalizeComparableAuthIdPConfig(idpConfig) {
2125
+ const configCase = idpConfig.config?.config?.case;
2126
+ const oidcValue = configCase === "oidc" && typeof idpConfig.config?.config?.value === "object" && idpConfig.config.config.value !== null ? idpConfig.config.config.value : void 0;
2127
+ return normalizeProtoConfig({
2128
+ name: idpConfig.name,
2129
+ authType: idpConfig.authType,
2130
+ config: configCase === "oidc" ? { config: {
2131
+ case: "oidc",
2132
+ value: {
2133
+ ...oidcValue ?? {},
2134
+ issuerUrl: oidcValue && "issuerUrl" in oidcValue ? oidcValue.issuerUrl || void 0 : void 0
2135
+ }
2136
+ } } : idpConfig.config
2137
+ });
2138
+ }
2139
+ function areAuthIdPConfigsEqual(existing, desired) {
2140
+ return areNormalizedEqual(normalizeComparableAuthIdPConfig(existing), normalizeComparableAuthIdPConfig(desired));
2141
+ }
1901
2142
  function protoIdPConfig(idpConfig) {
1902
2143
  switch (idpConfig.kind) {
1903
2144
  case "IDToken": return {
@@ -1950,6 +2191,11 @@ function protoIdPConfig(idpConfig) {
1950
2191
  }
1951
2192
  }
1952
2193
  async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
2194
+ const config = await tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig);
2195
+ if (!config) throw new Error(`Built-in IdP "${builtinIdPConfig.namespace}" not found. Please ensure that idp is configured correctly.`);
2196
+ return config;
2197
+ }
2198
+ async function tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1953
2199
  let idpService;
1954
2200
  try {
1955
2201
  idpService = await client.getIdPService({
@@ -1957,14 +2203,20 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1957
2203
  namespaceName: builtinIdPConfig.namespace
1958
2204
  });
1959
2205
  } catch (error) {
1960
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Built-in IdP "${builtinIdPConfig.namespace}" not found. Please ensure that idp is configured correctly.`);
2206
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
2207
+ throw error;
2208
+ }
2209
+ let idpClient;
2210
+ try {
2211
+ idpClient = await client.getIdPClient({
2212
+ workspaceId,
2213
+ namespaceName: builtinIdPConfig.namespace,
2214
+ name: builtinIdPConfig.clientName
2215
+ });
2216
+ } catch (error) {
2217
+ if (error instanceof ConnectError && error.code === Code.NotFound) return;
1961
2218
  throw error;
1962
2219
  }
1963
- const idpClient = await client.getIdPClient({
1964
- workspaceId,
1965
- namespaceName: builtinIdPConfig.namespace,
1966
- name: builtinIdPConfig.clientName
1967
- });
1968
2220
  const vaultName = idpClientVaultName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1969
2221
  const secretKey = idpClientSecretName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
1970
2222
  return { config: {
@@ -1980,16 +2232,35 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
1980
2232
  }
1981
2233
  } };
1982
2234
  }
1983
- async function planUserProfileConfigs(client, workspaceId, auths, deletedServices) {
2235
+ async function planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
1984
2236
  const changeSet = createChangeSet("Auth userProfileConfigs");
1985
2237
  for (const auth of auths) {
1986
2238
  const { parsedConfig: config } = auth;
1987
2239
  const name = `${config.name}-user-profile-config`;
1988
2240
  try {
1989
- await client.getUserProfileConfig({
2241
+ const { userProfileProviderConfig } = await client.getUserProfileConfig({
1990
2242
  workspaceId,
1991
2243
  namespaceName: config.name
1992
2244
  });
2245
+ const userProfileForUpdate = auth.userProfile;
2246
+ if (userProfileForUpdate) {
2247
+ const desired = protoUserProfileConfig(userProfileForUpdate);
2248
+ if (!forceApplyAll && areUserProfileConfigsEqual(userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
2249
+ else changeSet.updates.push({
2250
+ name,
2251
+ request: {
2252
+ workspaceId,
2253
+ namespaceName: config.name,
2254
+ userProfileProviderConfig: desired
2255
+ }
2256
+ });
2257
+ } else changeSet.deletes.push({
2258
+ name,
2259
+ request: {
2260
+ workspaceId,
2261
+ namespaceName: config.name
2262
+ }
2263
+ });
1993
2264
  } catch (error) {
1994
2265
  if (error instanceof ConnectError && error.code === Code.NotFound) {
1995
2266
  const userProfileForCreate = auth.userProfile;
@@ -2005,22 +2276,6 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
2005
2276
  }
2006
2277
  throw error;
2007
2278
  }
2008
- const userProfileForUpdate = auth.userProfile;
2009
- if (userProfileForUpdate) changeSet.updates.push({
2010
- name,
2011
- request: {
2012
- workspaceId,
2013
- namespaceName: config.name,
2014
- userProfileProviderConfig: protoUserProfileConfig(userProfileForUpdate)
2015
- }
2016
- });
2017
- else changeSet.deletes.push({
2018
- name,
2019
- request: {
2020
- workspaceId,
2021
- namespaceName: config.name
2022
- }
2023
- });
2024
2279
  }
2025
2280
  for (const namespaceName of deletedServices) {
2026
2281
  try {
@@ -2060,16 +2315,34 @@ function protoUserProfileConfig(userProfile) {
2060
2315
  } }
2061
2316
  };
2062
2317
  }
2063
- async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2318
+ async function planTenantConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2064
2319
  const changeSet = createChangeSet("Auth tenantConfigs");
2065
2320
  for (const auth of auths) {
2066
2321
  const { parsedConfig: config } = auth;
2067
2322
  const name = `${config.name}-tenant-config`;
2068
2323
  try {
2069
- await client.getTenantConfig({
2324
+ const { tenantProviderConfig } = await client.getTenantConfig({
2070
2325
  workspaceId,
2071
2326
  namespaceName: config.name
2072
2327
  });
2328
+ if (config.tenantProvider) {
2329
+ const desired = protoTenantConfig(config.tenantProvider);
2330
+ if (!forceApplyAll && areTenantProviderConfigsEqual(tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
2331
+ else changeSet.updates.push({
2332
+ name,
2333
+ request: {
2334
+ workspaceId,
2335
+ namespaceName: config.name,
2336
+ tenantProviderConfig: desired
2337
+ }
2338
+ });
2339
+ } else changeSet.deletes.push({
2340
+ name,
2341
+ request: {
2342
+ workspaceId,
2343
+ namespaceName: config.name
2344
+ }
2345
+ });
2073
2346
  } catch (error) {
2074
2347
  if (error instanceof ConnectError && error.code === Code.NotFound) {
2075
2348
  if (config.tenantProvider) changeSet.creates.push({
@@ -2084,21 +2357,6 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices) {
2084
2357
  }
2085
2358
  throw error;
2086
2359
  }
2087
- if (config.tenantProvider) changeSet.updates.push({
2088
- name,
2089
- request: {
2090
- workspaceId,
2091
- namespaceName: config.name,
2092
- tenantProviderConfig: protoTenantConfig(config.tenantProvider)
2093
- }
2094
- });
2095
- else changeSet.deletes.push({
2096
- name,
2097
- request: {
2098
- workspaceId,
2099
- namespaceName: config.name
2100
- }
2101
- });
2102
2360
  }
2103
2361
  for (const namespaceName of deletedServices) {
2104
2362
  try {
@@ -2133,7 +2391,7 @@ function protoTenantConfig(tenantConfig) {
2133
2391
  } }
2134
2392
  };
2135
2393
  }
2136
- async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2394
+ async function planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2137
2395
  const changeSet = createChangeSet("Auth machineUsers");
2138
2396
  const fetchMachineUsers = (authNamespace) => {
2139
2397
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2154,25 +2412,31 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2154
2412
  for (const auth of auths) {
2155
2413
  const { parsedConfig: config } = auth;
2156
2414
  const existingMachineUsers = await fetchMachineUsers(config.name);
2157
- const existingNameSet = /* @__PURE__ */ new Set();
2415
+ const existingMap = /* @__PURE__ */ new Map();
2158
2416
  existingMachineUsers.forEach((machineUser) => {
2159
- existingNameSet.add(machineUser.name);
2417
+ existingMap.set(machineUser.name, machineUser);
2160
2418
  });
2161
2419
  for (const machineUsername of Object.keys(config.machineUsers ?? {})) {
2162
2420
  const machineUser = config.machineUsers?.[machineUsername];
2163
2421
  if (!machineUser) continue;
2164
- if (existingNameSet.has(machineUsername)) {
2165
- changeSet.updates.push({
2422
+ const desiredMachineUser = {
2423
+ attributes: machineUser.attributeList,
2424
+ attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2425
+ };
2426
+ const existing = existingMap.get(machineUsername);
2427
+ if (existing) {
2428
+ if (!forceApplyAll && areMachineUsersEqual(existing, desiredMachineUser)) changeSet.unchanged.push({ name: machineUsername });
2429
+ else changeSet.updates.push({
2166
2430
  name: machineUsername,
2167
2431
  request: {
2168
2432
  workspaceId,
2169
2433
  authNamespace: config.name,
2170
2434
  name: machineUsername,
2171
2435
  attributes: machineUser.attributeList,
2172
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2436
+ attributeMap: desiredMachineUser.attributeMap
2173
2437
  }
2174
2438
  });
2175
- existingNameSet.delete(machineUsername);
2439
+ existingMap.delete(machineUsername);
2176
2440
  } else changeSet.creates.push({
2177
2441
  name: machineUsername,
2178
2442
  request: {
@@ -2180,11 +2444,11 @@ async function planMachineUsers(client, workspaceId, auths, deletedServices) {
2180
2444
  authNamespace: config.name,
2181
2445
  name: machineUsername,
2182
2446
  attributes: machineUser.attributeList,
2183
- attributeMap: machineUser.attributes ? protoMachineUserAttributeMap(machineUser.attributes) : void 0
2447
+ attributeMap: desiredMachineUser.attributeMap
2184
2448
  }
2185
2449
  });
2186
2450
  }
2187
- existingNameSet.forEach((name) => {
2451
+ existingMap.forEach((_, name) => {
2188
2452
  changeSet.deletes.push({
2189
2453
  name,
2190
2454
  request: {
@@ -2212,7 +2476,60 @@ function protoMachineUserAttributeMap(attributeMap) {
2212
2476
  for (const [key, value] of Object.entries(attributeMap)) ret[key] = fromJson(ValueSchema, value ?? null);
2213
2477
  return ret;
2214
2478
  }
2215
- async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2479
+ function normalizeComparableUserProfileConfig(config) {
2480
+ const comparableConfig = config.config?.config;
2481
+ const tailorDBConfig = comparableConfig?.case === "tailordb" ? comparableConfig.value : void 0;
2482
+ return normalizeProtoConfig({
2483
+ providerType: config.providerType,
2484
+ config: { config: {
2485
+ case: comparableConfig?.case,
2486
+ value: tailorDBConfig ? {
2487
+ ...tailorDBConfig,
2488
+ tenantIdField: tailorDBConfig.tenantIdField || void 0,
2489
+ attributesFields: normalizeStringArray(tailorDBConfig.attributesFields),
2490
+ attributeMap: normalizeProtoConfig(tailorDBConfig.attributeMap)
2491
+ } : comparableConfig?.value
2492
+ } }
2493
+ });
2494
+ }
2495
+ function areUserProfileConfigsEqual(existing, desired) {
2496
+ return areNormalizedEqual(normalizeComparableUserProfileConfig(existing), normalizeComparableUserProfileConfig(desired));
2497
+ }
2498
+ function normalizeComparableTenantProviderConfig(config) {
2499
+ return normalizeProtoConfig(config);
2500
+ }
2501
+ function areTenantProviderConfigsEqual(existing, desired) {
2502
+ return areNormalizedEqual(normalizeComparableTenantProviderConfig(existing), normalizeComparableTenantProviderConfig(desired));
2503
+ }
2504
+ function normalizeComparableMachineUser(input) {
2505
+ return normalizeProtoConfig({
2506
+ attributes: normalizeStringArray(input.attributes),
2507
+ attributeMap: normalizeProtoConfig(input.attributeMap ?? {})
2508
+ });
2509
+ }
2510
+ function areMachineUsersEqual(existing, desired) {
2511
+ return areNormalizedEqual(normalizeComparableMachineUser(existing), normalizeComparableMachineUser(desired));
2512
+ }
2513
+ function normalizeComparableOAuth2Client(client) {
2514
+ const accessTokenLifetime = oauth2LifetimeToSeconds(client.accessTokenLifetime);
2515
+ const refreshTokenLifetime = oauth2LifetimeToSeconds(client.refreshTokenLifetime);
2516
+ return normalizeProtoConfig({
2517
+ ...client,
2518
+ redirectUris: normalizeStringArray(client.redirectUris),
2519
+ grantTypes: [...client.grantTypes ?? []].sort((left, right) => left - right),
2520
+ accessTokenLifetime: accessTokenLifetime ?? 86400,
2521
+ refreshTokenLifetime: refreshTokenLifetime ?? 604800,
2522
+ requireDpop: client.requireDpop ?? false
2523
+ });
2524
+ }
2525
+ function oauth2LifetimeToSeconds(lifetime) {
2526
+ if (typeof lifetime === "number") return lifetime;
2527
+ if (lifetime?.seconds != null) return Number(lifetime.seconds);
2528
+ }
2529
+ function areOAuth2ClientsEqual(existing, desired) {
2530
+ return areNormalizedEqual(normalizeComparableOAuth2Client(existing), normalizeComparableOAuth2Client(desired));
2531
+ }
2532
+ async function planOAuth2Clients(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2216
2533
  const changeSet = createChangeSet("Auth oauth2Clients");
2217
2534
  const fetchOAuth2Clients = (namespaceName) => {
2218
2535
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -2235,14 +2552,16 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2235
2552
  const existingOAuth2Clients = await fetchOAuth2Clients(config.name);
2236
2553
  const existingClientsMap = /* @__PURE__ */ new Map();
2237
2554
  existingOAuth2Clients.forEach((oauth2Client) => {
2238
- existingClientsMap.set(oauth2Client.name, oauth2Client.clientType);
2555
+ existingClientsMap.set(oauth2Client.name, oauth2Client);
2239
2556
  });
2240
2557
  for (const oauth2ClientName of Object.keys(config.oauth2Clients ?? {})) {
2241
2558
  const oauth2Client = config.oauth2Clients?.[oauth2ClientName];
2242
2559
  if (!oauth2Client) continue;
2243
2560
  const newOAuth2Client = protoOAuth2Client(oauth2ClientName, oauth2Client);
2561
+ const resolvedRedirectUris = await resolveStaticWebsiteUrls(client, workspaceId, newOAuth2Client.redirectUris ?? [], "OAuth2 redirect URIs");
2244
2562
  if (existingClientsMap.has(oauth2ClientName)) {
2245
- if (existingClientsMap.get(oauth2ClientName) !== newOAuth2Client.clientType) changeSet.replaces.push({
2563
+ const existingClient = existingClientsMap.get(oauth2ClientName);
2564
+ if (existingClient.clientType !== newOAuth2Client.clientType) changeSet.replaces.push({
2246
2565
  name: oauth2ClientName,
2247
2566
  deleteRequest: {
2248
2567
  workspaceId,
@@ -2255,14 +2574,33 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
2255
2574
  oauth2Client: newOAuth2Client
2256
2575
  }
2257
2576
  });
2258
- else changeSet.updates.push({
2259
- name: oauth2ClientName,
2260
- request: {
2261
- workspaceId,
2262
- namespaceName: config.name,
2263
- oauth2Client: newOAuth2Client
2264
- }
2265
- });
2577
+ else {
2578
+ const desiredComparable = {
2579
+ ...newOAuth2Client,
2580
+ redirectUris: resolvedRedirectUris,
2581
+ accessTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.accessTokenLifetime),
2582
+ refreshTokenLifetime: oauth2LifetimeToSeconds(newOAuth2Client.refreshTokenLifetime)
2583
+ };
2584
+ const existingComparable = {
2585
+ name: existingClient.name,
2586
+ description: existingClient.description,
2587
+ grantTypes: existingClient.grantTypes,
2588
+ redirectUris: existingClient.redirectUris,
2589
+ clientType: existingClient.clientType,
2590
+ accessTokenLifetime: oauth2LifetimeToSeconds(existingClient.accessTokenLifetime),
2591
+ refreshTokenLifetime: oauth2LifetimeToSeconds(existingClient.refreshTokenLifetime),
2592
+ requireDpop: existingClient.requireDpop
2593
+ };
2594
+ if (!forceApplyAll && areOAuth2ClientsEqual(existingComparable, desiredComparable)) changeSet.unchanged.push({ name: oauth2ClientName });
2595
+ else changeSet.updates.push({
2596
+ name: oauth2ClientName,
2597
+ request: {
2598
+ workspaceId,
2599
+ namespaceName: config.name,
2600
+ oauth2Client: newOAuth2Client
2601
+ }
2602
+ });
2603
+ }
2266
2604
  existingClientsMap.delete(oauth2ClientName);
2267
2605
  } else changeSet.creates.push({
2268
2606
  name: oauth2ClientName,
@@ -2537,21 +2875,36 @@ function protoSCIMAttribute(attr) {
2537
2875
  subAttributes: attr.subAttributes?.map((attr) => protoSCIMAttribute(attr))
2538
2876
  };
2539
2877
  }
2540
- async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2878
+ function areAuthHooksEqual(existing, desired) {
2879
+ return areNormalizedEqual({
2880
+ scriptRef: existing.scriptRef ?? "",
2881
+ invoker: existing.invoker ? {
2882
+ namespace: existing.invoker.namespace ?? "",
2883
+ machineUserName: existing.invoker.machineUserName ?? ""
2884
+ } : void 0
2885
+ }, {
2886
+ scriptRef: desired.scriptRef ?? "",
2887
+ invoker: desired.invoker ? {
2888
+ namespace: desired.invoker.namespace ?? "",
2889
+ machineUserName: desired.invoker.machineUserName ?? ""
2890
+ } : void 0
2891
+ });
2892
+ }
2893
+ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
2541
2894
  const changeSet = createChangeSet("Auth hooks");
2542
2895
  for (const auth of auths) {
2543
2896
  const { parsedConfig: config } = auth;
2544
2897
  const beforeLogin = config.hooks?.beforeLogin;
2545
2898
  let existingHook;
2546
2899
  try {
2547
- await client.getAuthHook({
2900
+ const { hook } = await client.getAuthHook({
2548
2901
  workspaceId,
2549
2902
  namespaceName: config.name,
2550
2903
  hookPoint: AuthHookPoint.BEFORE_LOGIN
2551
2904
  });
2552
- existingHook = true;
2905
+ existingHook = hook;
2553
2906
  } catch (error) {
2554
- if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = false;
2907
+ if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = void 0;
2555
2908
  else throw error;
2556
2909
  }
2557
2910
  if (beforeLogin) {
@@ -2567,7 +2920,8 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices) {
2567
2920
  }
2568
2921
  }
2569
2922
  };
2570
- if (existingHook) changeSet.updates.push({
2923
+ if (existingHook) if (!forceApplyAll && areAuthHooksEqual(existingHook, hookRequest.hook)) changeSet.unchanged.push({ name: `${config.name}/before-login` });
2924
+ else changeSet.updates.push({
2571
2925
  name: `${config.name}/before-login`,
2572
2926
  request: hookRequest
2573
2927
  });
@@ -2750,19 +3104,10 @@ const ACTOR_TRANSFORM_EXPR = "actor: args.actor ? (({ attributeMap, attributes:
2750
3104
  function buildExecutorArgsExpr(triggerKind, env) {
2751
3105
  const envExpr = `env: ${JSON.stringify(env)}`;
2752
3106
  switch (triggerKind) {
2753
- case "schedule":
2754
- case "recordCreated":
2755
- case "recordUpdated":
2756
- case "recordDeleted":
2757
- case "idpUserCreated":
2758
- case "idpUserUpdated":
2759
- case "idpUserDeleted":
2760
- case "authAccessTokenIssued":
2761
- case "authAccessTokenRefreshed":
2762
- case "authAccessTokenRevoked": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
3107
+ case "schedule": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2763
3108
  case "resolverExecuted": return `({ ...args, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, success: !!args.succeeded, result: args.succeeded?.result.resolver, error: args.failed?.error, ${envExpr} })`;
2764
3109
  case "incomingWebhook": return `({ ...args, appNamespace: args.namespaceName, rawBody: args.raw_body, ${envExpr} })`;
2765
- default: throw new Error(`Unknown trigger kind for args expression: ${triggerKind}`);
3110
+ default: return `({ ...args, event: args.eventType?.split(".").pop(), rawEvent: args.eventType, appNamespace: args.namespaceName, ${ACTOR_TRANSFORM_EXPR}, ${envExpr} })`;
2766
3111
  }
2767
3112
  }
2768
3113
  /**
@@ -2832,13 +3177,15 @@ async function planExecutor(context) {
2832
3177
  const { metadata } = await client.getMetadata({ trn: trn$3(workspaceId, resource.name) });
2833
3178
  existingExecutors[resource.name] = {
2834
3179
  resource,
2835
- label: metadata?.labels[sdkNameLabelKey]
3180
+ label: metadata?.labels[sdkNameLabelKey],
3181
+ allLabels: metadata?.labels
2836
3182
  };
2837
3183
  }));
2838
3184
  const executors = forRemoval ? {} : await application.executorService?.loadExecutors() ?? {};
2839
3185
  for (const executor of Object.values(executors)) {
2840
3186
  const existing = existingExecutors[executor.name];
2841
3187
  const metaRequest = await buildMetaRequest(trn$3(workspaceId, executor.name), application.name);
3188
+ const desiredExecutor = protoExecutor(application, executor);
2842
3189
  if (existing) {
2843
3190
  if (!existing.label) unmanaged.push({
2844
3191
  resourceType: "Executor",
@@ -2849,11 +3196,12 @@ async function planExecutor(context) {
2849
3196
  resourceName: executor.name,
2850
3197
  currentOwner: existing.label
2851
3198
  });
2852
- changeSet.updates.push({
3199
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areExecutorsEqual(existing.resource, desiredExecutor)) changeSet.unchanged.push({ name: executor.name });
3200
+ else changeSet.updates.push({
2853
3201
  name: executor.name,
2854
3202
  request: {
2855
3203
  workspaceId,
2856
- executor: protoExecutor(application, executor)
3204
+ executor: desiredExecutor
2857
3205
  },
2858
3206
  metaRequest
2859
3207
  });
@@ -2862,7 +3210,7 @@ async function planExecutor(context) {
2862
3210
  name: executor.name,
2863
3211
  request: {
2864
3212
  workspaceId,
2865
- executor: protoExecutor(application, executor)
3213
+ executor: desiredExecutor
2866
3214
  },
2867
3215
  metaRequest
2868
3216
  });
@@ -2886,6 +3234,56 @@ async function planExecutor(context) {
2886
3234
  resourceOwners
2887
3235
  };
2888
3236
  }
3237
+ function normalizeComparableExecutor(executor) {
3238
+ const normalized = normalizeProtoConfig(executor) ?? {};
3239
+ const webhookHeaders = normalized.targetConfig?.config?.case === "webhook" ? [...normalized.targetConfig.config.value.headers ?? []].sort((left, right) => (left.key ?? "").localeCompare(right.key ?? "")) : void 0;
3240
+ const triggerConfig = normalized.triggerConfig?.config?.case === "incomingWebhook" ? {
3241
+ ...normalized.triggerConfig,
3242
+ config: {
3243
+ ...normalized.triggerConfig.config,
3244
+ value: {}
3245
+ }
3246
+ } : normalized.triggerConfig?.config?.case === "event" ? {
3247
+ ...normalized.triggerConfig,
3248
+ config: {
3249
+ ...normalized.triggerConfig.config,
3250
+ value: {
3251
+ ...normalized.triggerConfig.config.value,
3252
+ eventType: void 0
3253
+ }
3254
+ }
3255
+ } : normalized.triggerConfig;
3256
+ return {
3257
+ name: normalized.name,
3258
+ description: normalized.description ?? "",
3259
+ disabled: normalized.disabled ?? false,
3260
+ triggerType: normalized.triggerType,
3261
+ triggerConfig,
3262
+ targetType: normalized.targetType,
3263
+ targetConfig: normalized.targetConfig?.config?.case === "webhook" ? {
3264
+ ...normalized.targetConfig,
3265
+ config: {
3266
+ ...normalized.targetConfig.config,
3267
+ value: {
3268
+ ...normalized.targetConfig.config.value,
3269
+ headers: webhookHeaders
3270
+ }
3271
+ }
3272
+ } : normalized.targetConfig?.config?.case === "function" ? {
3273
+ ...normalized.targetConfig,
3274
+ config: {
3275
+ ...normalized.targetConfig.config,
3276
+ value: {
3277
+ ...normalized.targetConfig.config.value,
3278
+ script: void 0
3279
+ }
3280
+ }
3281
+ } : normalized.targetConfig
3282
+ };
3283
+ }
3284
+ function areExecutorsEqual(existing, desired) {
3285
+ return areNormalizedEqual(normalizeComparableExecutor(existing), normalizeComparableExecutor(desired));
3286
+ }
2889
3287
  function resolveTailorDBNamespace(application, typeName) {
2890
3288
  for (const service of application.tailorDBServices) if (service.types[typeName]) return service.namespace;
2891
3289
  throw new Error(`TailorDB type "${typeName}" not found in any namespace. Available namespaces: ${application.tailorDBServices.map((s) => s.namespace).join(", ")}`);
@@ -2910,18 +3308,6 @@ function protoExecutor(application, executor) {
2910
3308
  let triggerType;
2911
3309
  let triggerConfig;
2912
3310
  const argsExpr = buildExecutorArgsExpr(trigger.kind, env);
2913
- const eventType = {
2914
- recordCreated: "tailordb.type_record.created",
2915
- recordUpdated: "tailordb.type_record.updated",
2916
- recordDeleted: "tailordb.type_record.deleted",
2917
- resolverExecuted: "pipeline.resolver.executed",
2918
- idpUserCreated: "idp.user.created",
2919
- idpUserUpdated: "idp.user.updated",
2920
- idpUserDeleted: "idp.user.deleted",
2921
- authAccessTokenIssued: "auth.access_token.issued",
2922
- authAccessTokenRefreshed: "auth.access_token.refreshed",
2923
- authAccessTokenRevoked: "auth.access_token.revoked"
2924
- };
2925
3311
  function typedEventTrigger(typedConfig) {
2926
3312
  return { config: {
2927
3313
  case: "event",
@@ -2939,14 +3325,12 @@ function protoExecutor(application, executor) {
2939
3325
  }
2940
3326
  } };
2941
3327
  break;
2942
- case "recordCreated":
2943
- case "recordUpdated":
2944
- case "recordDeleted":
3328
+ case "tailordb":
2945
3329
  triggerType = ExecutorTriggerType.EVENT;
2946
3330
  triggerConfig = typedEventTrigger({
2947
3331
  case: "tailordb",
2948
3332
  value: {
2949
- eventTypes: [eventType[trigger.kind]],
3333
+ eventTypes: trigger.events,
2950
3334
  namespaceName: resolveTailorDBNamespace(application, trigger.typeName),
2951
3335
  typeName: trigger.typeName,
2952
3336
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2958,7 +3342,7 @@ function protoExecutor(application, executor) {
2958
3342
  triggerConfig = typedEventTrigger({
2959
3343
  case: "pipeline",
2960
3344
  value: {
2961
- eventTypes: [eventType[trigger.kind]],
3345
+ eventTypes: ["pipeline.resolver.executed"],
2962
3346
  namespaceName: resolveResolverNamespace(application, trigger.resolverName),
2963
3347
  resolverName: trigger.resolverName,
2964
3348
  ...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
@@ -2972,26 +3356,22 @@ function protoExecutor(application, executor) {
2972
3356
  value: {}
2973
3357
  } };
2974
3358
  break;
2975
- case "idpUserCreated":
2976
- case "idpUserUpdated":
2977
- case "idpUserDeleted":
3359
+ case "idpUser":
2978
3360
  triggerType = ExecutorTriggerType.EVENT;
2979
3361
  triggerConfig = typedEventTrigger({
2980
3362
  case: "idp",
2981
3363
  value: {
2982
- eventTypes: [eventType[trigger.kind]],
3364
+ eventTypes: trigger.events,
2983
3365
  namespaceName: resolveIdpNamespace(application)
2984
3366
  }
2985
3367
  });
2986
3368
  break;
2987
- case "authAccessTokenIssued":
2988
- case "authAccessTokenRefreshed":
2989
- case "authAccessTokenRevoked":
3369
+ case "authAccessToken":
2990
3370
  triggerType = ExecutorTriggerType.EVENT;
2991
3371
  triggerConfig = typedEventTrigger({
2992
3372
  case: "auth",
2993
3373
  value: {
2994
- eventTypes: [eventType[trigger.kind]],
3374
+ eventTypes: trigger.events,
2995
3375
  namespaceName: resolveAuthNamespace(application)
2996
3376
  }
2997
3377
  });
@@ -3147,7 +3527,7 @@ async function applyPipeline(client, result, phase = "create-update") {
3147
3527
  * @returns Planned changes
3148
3528
  */
3149
3529
  async function planPipeline(context) {
3150
- const { client, workspaceId, application, forRemoval } = context;
3530
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3151
3531
  const pipelines = [];
3152
3532
  if (!forRemoval) for (const pipeline of application.resolverServices) {
3153
3533
  await pipeline.loadResolvers();
@@ -3155,7 +3535,7 @@ async function planPipeline(context) {
3155
3535
  }
3156
3536
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
3157
3537
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, pipelines);
3158
- const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env);
3538
+ const resolverChangeSet = await planResolvers(client, workspaceId, pipelines, executors, serviceChangeSet.deletes.map((del) => del.name), application.env, forceApplyAll);
3159
3539
  serviceChangeSet.print();
3160
3540
  resolverChangeSet.print();
3161
3541
  return {
@@ -3195,7 +3575,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3195
3575
  const { metadata } = await client.getMetadata({ trn: trn$2(workspaceId, resource.namespace.name) });
3196
3576
  existingServices[resource.namespace.name] = {
3197
3577
  resource,
3198
- label: metadata?.labels[sdkNameLabelKey]
3578
+ label: metadata?.labels[sdkNameLabelKey],
3579
+ allLabels: metadata?.labels
3199
3580
  };
3200
3581
  }));
3201
3582
  for (const pipeline of pipelines) {
@@ -3211,7 +3592,8 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3211
3592
  resourceName: pipeline.namespace,
3212
3593
  currentOwner: existing.label
3213
3594
  });
3214
- changeSet.updates.push({
3595
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
3596
+ else changeSet.updates.push({
3215
3597
  name: pipeline.namespace,
3216
3598
  request: {
3217
3599
  workspaceId,
@@ -3247,7 +3629,7 @@ async function planServices$1(client, workspaceId, appName, pipelines) {
3247
3629
  resourceOwners
3248
3630
  };
3249
3631
  }
3250
- async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env) {
3632
+ async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, forceApplyAll = false) {
3251
3633
  const changeSet = createChangeSet("Pipeline resolvers");
3252
3634
  const fetchResolvers = (namespaceName) => {
3253
3635
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -3270,29 +3652,35 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3270
3652
  for (const pipeline of pipelines) for (const resolver of Object.values(pipeline.resolvers)) if (executorUsedResolvers.has(resolver.name) && resolver.publishEvents === false) throw new Error(`Resolver "${resolver.name}" has publishEvents set to false, but it is used by an executor with a resolverExecuted trigger. Either remove the publishEvents: false setting or remove the executor trigger for this resolver.`);
3271
3653
  for (const pipeline of pipelines) {
3272
3654
  const existingResolvers = await fetchResolvers(pipeline.namespace);
3273
- const existingNameSet = /* @__PURE__ */ new Set();
3274
- existingResolvers.forEach((resolver) => {
3275
- existingNameSet.add(resolver.name);
3276
- });
3277
- for (const resolver of Object.values(pipeline.resolvers)) if (existingNameSet.has(resolver.name)) {
3278
- changeSet.updates.push({
3655
+ const existingResolversMap = new Map(existingResolvers.map((resolver) => [resolver.name, resolver]));
3656
+ for (const resolver of Object.values(pipeline.resolvers)) {
3657
+ const desiredResolver = processResolver(pipeline.namespace, resolver, executorUsedResolvers, env);
3658
+ if (existingResolversMap.get(resolver.name)) {
3659
+ const { pipelineResolver: existingResolverDetail } = await client.getPipelineResolver({
3660
+ workspaceId,
3661
+ namespaceName: pipeline.namespace,
3662
+ resolverName: resolver.name
3663
+ });
3664
+ if (!forceApplyAll && existingResolverDetail && areResolversEqual(existingResolverDetail, desiredResolver)) changeSet.unchanged.push({ name: resolver.name });
3665
+ else changeSet.updates.push({
3666
+ name: resolver.name,
3667
+ request: {
3668
+ workspaceId,
3669
+ namespaceName: pipeline.namespace,
3670
+ pipelineResolver: desiredResolver
3671
+ }
3672
+ });
3673
+ existingResolversMap.delete(resolver.name);
3674
+ } else changeSet.creates.push({
3279
3675
  name: resolver.name,
3280
3676
  request: {
3281
3677
  workspaceId,
3282
3678
  namespaceName: pipeline.namespace,
3283
- pipelineResolver: processResolver(pipeline.namespace, resolver, executorUsedResolvers, env)
3679
+ pipelineResolver: desiredResolver
3284
3680
  }
3285
3681
  });
3286
- existingNameSet.delete(resolver.name);
3287
- } else changeSet.creates.push({
3288
- name: resolver.name,
3289
- request: {
3290
- workspaceId,
3291
- namespaceName: pipeline.namespace,
3292
- pipelineResolver: processResolver(pipeline.namespace, resolver, executorUsedResolvers, env)
3293
- }
3294
- });
3295
- existingNameSet.forEach((name) => {
3682
+ }
3683
+ existingResolversMap.forEach((_resolver, name) => {
3296
3684
  changeSet.deletes.push({
3297
3685
  name,
3298
3686
  request: {
@@ -3315,6 +3703,59 @@ async function planResolvers(client, workspaceId, pipelines, executors, deletedS
3315
3703
  });
3316
3704
  return changeSet;
3317
3705
  }
3706
+ function normalizeComparableResolver(resolver) {
3707
+ const normalized = normalizeProtoConfig(resolver) ?? {};
3708
+ return {
3709
+ name: normalized.name,
3710
+ description: normalized.description ?? "",
3711
+ authorization: normalized.authorization ?? "",
3712
+ operationType: normalized.operationType,
3713
+ publishExecutionEvents: normalized.publishExecutionEvents ?? false,
3714
+ inputs: normalizeComparableFields(normalized.inputs),
3715
+ response: normalizeComparableField(normalized.response),
3716
+ pipelines: normalizeComparablePipelines(normalized.pipelines)
3717
+ };
3718
+ }
3719
+ function areResolversEqual(existing, desired) {
3720
+ return areNormalizedEqual(normalizeComparableResolver(existing), normalizeComparableResolver(desired));
3721
+ }
3722
+ function normalizeComparablePipelines(pipelines) {
3723
+ return (pipelines ?? []).map((pipeline) => ({
3724
+ name: pipeline.name ?? "",
3725
+ operationName: pipeline.operationName ?? "",
3726
+ description: pipeline.description ?? "",
3727
+ operationType: pipeline.operationType,
3728
+ operationSourceRef: pipeline.operationSourceRef ?? "",
3729
+ operationHook: pipeline.operationHook?.expr ?? "",
3730
+ postScript: pipeline.postScript ?? "",
3731
+ skipOperationOnError: pipeline.skipOperationOnError ?? false,
3732
+ invoker: pipeline.invoker ?? void 0
3733
+ }));
3734
+ }
3735
+ function normalizeComparableFields(fields) {
3736
+ return (fields ?? []).map((field) => normalizeComparableField(field));
3737
+ }
3738
+ function normalizeComparableField(field) {
3739
+ if (!field) return;
3740
+ return {
3741
+ name: field.name ?? "",
3742
+ array: field.array ?? false,
3743
+ required: field.required ?? true,
3744
+ description: field.description ?? "",
3745
+ type: normalizeComparableType(field.type)
3746
+ };
3747
+ }
3748
+ function normalizeComparableType(type) {
3749
+ if (!type) return;
3750
+ return {
3751
+ kind: type.kind ?? "",
3752
+ name: type.name ?? "",
3753
+ required: type.required ?? true,
3754
+ description: type.description ?? "",
3755
+ allowedValues: type.allowedValues ?? [],
3756
+ fields: (type.fields ?? []).map((field) => normalizeComparableField(field))
3757
+ };
3758
+ }
3318
3759
  function processResolver(namespace, resolver, executorUsedResolvers, env) {
3319
3760
  const pipelines = [{
3320
3761
  name: "body",
@@ -3430,7 +3871,7 @@ function hashValue(value) {
3430
3871
  * @returns Planned changes for vaults and secrets
3431
3872
  */
3432
3873
  async function planSecretManager(context) {
3433
- const { client, workspaceId, application, forRemoval } = context;
3874
+ const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
3434
3875
  const secretVaults = forRemoval ? [] : application.secrets;
3435
3876
  const vaultChangeSet = createChangeSet("Secret Manager vaults");
3436
3877
  const secretChangeSet = createChangeSet("Secret Manager secrets");
@@ -3452,10 +3893,11 @@ async function planSecretManager(context) {
3452
3893
  });
3453
3894
  const existingVaults = {};
3454
3895
  await Promise.all(existingVaultList.map(async (resource) => {
3455
- const { metadata } = await client.getMetadata({ trn: vaultTrn(workspaceId, resource.name) });
3896
+ const { metadata } = await client.getMetadata({ trn: vaultTrn$1(workspaceId, resource.name) });
3456
3897
  existingVaults[resource.name] = {
3457
3898
  resource,
3458
- label: metadata?.labels[sdkNameLabelKey]
3899
+ label: metadata?.labels[sdkNameLabelKey],
3900
+ allLabels: metadata?.labels
3459
3901
  };
3460
3902
  }));
3461
3903
  const state = loadSecretsState();
@@ -3463,6 +3905,7 @@ async function planSecretManager(context) {
3463
3905
  const vaultName = vault.vaultName;
3464
3906
  const existing = existingVaults[vaultName];
3465
3907
  if (existing) {
3908
+ const metaRequest = await buildMetaRequest(vaultTrn$1(workspaceId, vaultName), application.name);
3466
3909
  if (!existing.label) unmanaged.push({
3467
3910
  resourceType: "Secret Manager vault",
3468
3911
  resourceName: vaultName
@@ -3472,7 +3915,8 @@ async function planSecretManager(context) {
3472
3915
  resourceName: vaultName,
3473
3916
  currentOwner: existing.label
3474
3917
  });
3475
- vaultChangeSet.updates.push({
3918
+ if (existing.label === application.name && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
3919
+ else vaultChangeSet.updates.push({
3476
3920
  name: vaultName,
3477
3921
  workspaceId
3478
3922
  });
@@ -3498,7 +3942,9 @@ async function planSecretManager(context) {
3498
3942
  })).map((s) => s.name);
3499
3943
  const existingSet = new Set(existingSecrets);
3500
3944
  for (const secret of vault.secrets) if (existingSet.has(secret.name)) {
3501
- if (hashValue(secret.value) !== state.vaults[vaultName]?.[secret.name]) secretChangeSet.updates.push({
3945
+ const currentHash = hashValue(secret.value);
3946
+ const storedHash = state.vaults[vaultName]?.[secret.name];
3947
+ if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
3502
3948
  name: `${vaultName}/${secret.name}`,
3503
3949
  secretName: secret.name,
3504
3950
  workspaceId,
@@ -3561,7 +4007,7 @@ async function planSecretManager(context) {
3561
4007
  resourceOwners
3562
4008
  };
3563
4009
  }
3564
- function vaultTrn(workspaceId, name) {
4010
+ function vaultTrn$1(workspaceId, name) {
3565
4011
  return `trn:v1:workspace:${workspaceId}:vault:${name}`;
3566
4012
  }
3567
4013
  /**
@@ -3581,12 +4027,12 @@ async function applySecretManager(client, result, phase = "create-update", appli
3581
4027
  secretmanagerVaultName: create.name
3582
4028
  });
3583
4029
  if (application) {
3584
- const metaRequest = await buildMetaRequest(vaultTrn(create.workspaceId, create.name), application.name);
4030
+ const metaRequest = await buildMetaRequest(vaultTrn$1(create.workspaceId, create.name), application.name);
3585
4031
  await client.setMetadata(metaRequest);
3586
4032
  }
3587
4033
  }));
3588
4034
  if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
3589
- const metaRequest = await buildMetaRequest(vaultTrn(update.workspaceId, update.name), application.name);
4035
+ const metaRequest = await buildMetaRequest(vaultTrn$1(update.workspaceId, update.name), application.name);
3590
4036
  await client.setMetadata(metaRequest);
3591
4037
  }));
3592
4038
  await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret({
@@ -3654,6 +4100,21 @@ async function applyStaticWebsite(client, result, phase = "create-update") {
3654
4100
  function trn$1(workspaceId, name) {
3655
4101
  return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
3656
4102
  }
4103
+ function normalizeComparableStaticWebsiteShape(input) {
4104
+ return {
4105
+ description: input.description,
4106
+ allowedIpAddresses: [...input.allowedIpAddresses].sort()
4107
+ };
4108
+ }
4109
+ function normalizeComparableStaticWebsite(input) {
4110
+ return normalizeComparableStaticWebsiteShape({
4111
+ description: input.description || "",
4112
+ allowedIpAddresses: [...input.allowedIpAddresses || []]
4113
+ });
4114
+ }
4115
+ function areStaticWebsitesEqual(existing, desired) {
4116
+ return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
4117
+ }
3657
4118
  /**
3658
4119
  * Plan static website changes based on current and desired state.
3659
4120
  * @param context - Planning context
@@ -3683,7 +4144,8 @@ async function planStaticWebsite(context) {
3683
4144
  const { metadata } = await client.getMetadata({ trn: trn$1(workspaceId, resource.name) });
3684
4145
  existingWebsites[resource.name] = {
3685
4146
  resource,
3686
- label: metadata?.labels[sdkNameLabelKey]
4147
+ label: metadata?.labels[sdkNameLabelKey],
4148
+ allLabels: metadata?.labels
3687
4149
  };
3688
4150
  }));
3689
4151
  const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
@@ -3692,7 +4154,17 @@ async function planStaticWebsite(context) {
3692
4154
  const name = websiteService.name;
3693
4155
  const existing = existingWebsites[name];
3694
4156
  const metaRequest = await buildMetaRequest(trn$1(workspaceId, name), application.name);
4157
+ const desired = normalizeComparableStaticWebsite(config);
4158
+ const request = {
4159
+ workspaceId,
4160
+ staticwebsite: {
4161
+ name,
4162
+ description: config.description || "",
4163
+ allowedIpAddresses: config.allowedIpAddresses || []
4164
+ }
4165
+ };
3695
4166
  if (existing) {
4167
+ const isManagedByApp = existing.label === application.name;
3696
4168
  if (!existing.label) unmanaged.push({
3697
4169
  resourceType: "StaticWebsite",
3698
4170
  resourceName: name
@@ -3702,29 +4174,16 @@ async function planStaticWebsite(context) {
3702
4174
  resourceName: name,
3703
4175
  currentOwner: existing.label
3704
4176
  });
3705
- changeSet.updates.push({
4177
+ if (isManagedByApp && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
4178
+ else changeSet.updates.push({
3706
4179
  name,
3707
- request: {
3708
- workspaceId,
3709
- staticwebsite: {
3710
- name,
3711
- description: config.description || "",
3712
- allowedIpAddresses: config.allowedIpAddresses || []
3713
- }
3714
- },
4180
+ request,
3715
4181
  metaRequest
3716
4182
  });
3717
4183
  delete existingWebsites[name];
3718
4184
  } else changeSet.creates.push({
3719
4185
  name,
3720
- request: {
3721
- workspaceId,
3722
- staticwebsite: {
3723
- name,
3724
- description: config.description || "",
3725
- allowedIpAddresses: config.allowedIpAddresses || []
3726
- }
3727
- },
4186
+ request,
3728
4187
  metaRequest
3729
4188
  });
3730
4189
  }
@@ -3948,8 +4407,11 @@ const INITIAL_SCHEMA_NUMBER = 0;
3948
4407
  * Migration file names (used within migration directories)
3949
4408
  */
3950
4409
  const SCHEMA_FILE_NAME = "schema.json";
4410
+ /** File name for migration diff metadata. */
3951
4411
  const DIFF_FILE_NAME = "diff.json";
4412
+ /** File name for migration script. */
3952
4413
  const MIGRATE_FILE_NAME = "migrate.ts";
4414
+ /** File name for generated DB type definitions. */
3953
4415
  const DB_TYPES_FILE_NAME = "db.ts";
3954
4416
  /**
3955
4417
  * Pattern for validating migration number format (4-digit sequential number)
@@ -5555,13 +6017,12 @@ function buildMigrationContextForScripts(client, migrationContext, migrationsReq
5555
6017
  async function applyTailorDB(client, result, phase = "create-update") {
5556
6018
  const { changeSet, context: migrationContext } = result;
5557
6019
  if (phase === "create-update") {
5558
- let pendingMigrations = [];
5559
6020
  const typesByNamespace = /* @__PURE__ */ new Map();
5560
6021
  for (const tailordb of migrationContext.application.tailorDBServices) {
5561
6022
  const types = tailordb.types;
5562
6023
  if (types) typesByNamespace.set(tailordb.namespace, types);
5563
6024
  }
5564
- pendingMigrations = await validateAndDetectMigrations(client, migrationContext.workspaceId, typesByNamespace, migrationContext.config, migrationContext.noSchemaCheck);
6025
+ const pendingMigrations = await validateAndDetectMigrations(client, migrationContext.workspaceId, typesByNamespace, migrationContext.config, migrationContext.noSchemaCheck);
5565
6026
  if (pendingMigrations.length > 0) {
5566
6027
  processedTypes.reset();
5567
6028
  deletedResources.reset();
@@ -5848,7 +6309,7 @@ async function executeSingleMigrationPostPhase(client, changeSet, migration) {
5848
6309
  * @returns Planned changes
5849
6310
  */
5850
6311
  async function planTailorDB(context) {
5851
- const { client, workspaceId, application, forRemoval, config, noSchemaCheck } = context;
6312
+ const { client, workspaceId, application, forRemoval, config, noSchemaCheck, forceApplyAll = false } = context;
5852
6313
  const tailordbs = [];
5853
6314
  if (!forRemoval) for (const tailordb of application.tailorDBServices) {
5854
6315
  await tailordb.loadTypes();
@@ -5857,7 +6318,7 @@ async function planTailorDB(context) {
5857
6318
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
5858
6319
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
5859
6320
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
5860
- const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices), planGqlPermissions(client, workspaceId, tailordbs, deletedServices)]);
6321
+ const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
5861
6322
  serviceChangeSet.print();
5862
6323
  typeChangeSet.print();
5863
6324
  gqlPermissionChangeSet.print();
@@ -5881,6 +6342,21 @@ async function planTailorDB(context) {
5881
6342
  function trn(workspaceId, name) {
5882
6343
  return `${trnPrefix(workspaceId)}:tailordb:${name}`;
5883
6344
  }
6345
+ function normalizeComparableTailorDBService(service) {
6346
+ return normalizeProtoConfig({
6347
+ namespace: service.namespace,
6348
+ defaultTimezone: service.defaultTimezone || "UTC"
6349
+ });
6350
+ }
6351
+ function areTailorDBServicesEqual(existing, desired) {
6352
+ return areNormalizedEqual(normalizeComparableTailorDBService({
6353
+ namespace: existing.namespace?.name,
6354
+ defaultTimezone: existing.defaultTimezone
6355
+ }), normalizeComparableTailorDBService({
6356
+ namespace: desired.namespace,
6357
+ defaultTimezone: "UTC"
6358
+ }));
6359
+ }
5884
6360
  async function planServices(client, workspaceId, appName, tailordbs) {
5885
6361
  const changeSet = createChangeSet("TailorDB services");
5886
6362
  const conflicts = [];
@@ -5922,7 +6398,8 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5922
6398
  resourceName: tailordb.namespace,
5923
6399
  currentOwner: existing.label
5924
6400
  });
5925
- changeSet.updates.push({
6401
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
6402
+ else changeSet.updates.push({
5926
6403
  name: tailordb.namespace,
5927
6404
  metaRequest
5928
6405
  });
@@ -5955,7 +6432,7 @@ async function planServices(client, workspaceId, appName, tailordbs) {
5955
6432
  resourceOwners
5956
6433
  };
5957
6434
  }
5958
- async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace) {
6435
+ async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
5959
6436
  const changeSet = createChangeSet("TailorDB types");
5960
6437
  const fetchTypes = (namespaceName) => {
5961
6438
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -5974,7 +6451,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5974
6451
  });
5975
6452
  };
5976
6453
  const executorUsedTypes = /* @__PURE__ */ new Set();
5977
- for (const executor of executors) if (executor.trigger.kind === "recordCreated" || executor.trigger.kind === "recordUpdated" || executor.trigger.kind === "recordDeleted") executorUsedTypes.add(executor.trigger.typeName);
6454
+ for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
5978
6455
  for (const tailordb of tailordbs) {
5979
6456
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5980
6457
  for (const typeName of Object.keys(types)) {
@@ -5984,13 +6461,14 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5984
6461
  }
5985
6462
  for (const tailordb of tailordbs) {
5986
6463
  const existingTypes = await fetchTypes(tailordb.namespace);
5987
- const existingNameSet = /* @__PURE__ */ new Set();
5988
- existingTypes.forEach((type) => existingNameSet.add(type.name));
6464
+ const existingTypesMap = new Map(existingTypes.map((type) => [type.name, type]));
5989
6465
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
5990
6466
  for (const typeName of Object.keys(types)) {
5991
6467
  const tailordbType = generateTailorDBTypeManifest(types[typeName], executorUsedTypes, tailordb.config.gqlOperations);
5992
- if (existingNameSet.has(typeName)) {
5993
- changeSet.updates.push({
6468
+ const existingType = existingTypesMap.get(typeName);
6469
+ if (existingType) {
6470
+ if (!forceApplyAll && areNormalizedEqual(normalizeComparableTailorDBType(existingType), normalizeComparableTailorDBType(tailordbType))) changeSet.unchanged.push({ name: typeName });
6471
+ else changeSet.updates.push({
5994
6472
  name: typeName,
5995
6473
  request: {
5996
6474
  workspaceId,
@@ -5998,7 +6476,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
5998
6476
  tailordbType
5999
6477
  }
6000
6478
  });
6001
- existingNameSet.delete(typeName);
6479
+ existingTypesMap.delete(typeName);
6002
6480
  } else changeSet.creates.push({
6003
6481
  name: typeName,
6004
6482
  request: {
@@ -6008,7 +6486,7 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6008
6486
  }
6009
6487
  });
6010
6488
  }
6011
- existingNameSet.forEach((name) => {
6489
+ existingTypesMap.forEach((_type, name) => {
6012
6490
  changeSet.deletes.push({
6013
6491
  name,
6014
6492
  request: {
@@ -6031,6 +6509,66 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
6031
6509
  });
6032
6510
  return changeSet;
6033
6511
  }
6512
+ function isPlainObject(value) {
6513
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6514
+ }
6515
+ const tailordbCompareKnownDefaults = {
6516
+ disableGqlOperations: {
6517
+ create: false,
6518
+ update: false,
6519
+ delete: false,
6520
+ read: false
6521
+ },
6522
+ emptyExpression: "",
6523
+ numericStringPaths: new Set([
6524
+ "schema.fields.*.serial.start",
6525
+ "schema.fields.*.serial.maxValue",
6526
+ "schema.settings.defaultQueryLimitSize",
6527
+ "schema.settings.maxBulkUpsertSize"
6528
+ ])
6529
+ };
6530
+ function normalizeComparableTailorDBType(type) {
6531
+ const normalized = normalizeProtoConfig(type);
6532
+ return normalizeTailorDBCompareValue({
6533
+ name: normalized?.name ?? "",
6534
+ schema: {
6535
+ description: normalized?.schema?.description ?? "",
6536
+ fields: normalized?.schema?.fields ?? {},
6537
+ relationships: normalized?.schema?.relationships ?? {},
6538
+ settings: normalized?.schema?.settings ?? {},
6539
+ indexes: normalized?.schema?.indexes ?? {},
6540
+ files: normalized?.schema?.files ?? {},
6541
+ permission: normalized?.schema?.permission ?? {}
6542
+ }
6543
+ }, []);
6544
+ }
6545
+ function normalizeTailorDBCompareValue(value, path) {
6546
+ if (value === void 0 || value === null) return value;
6547
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "string") {
6548
+ if (matchesNumericStringPath(path) && isNumericLikeValue(value)) return String(value);
6549
+ if (path.at(-1) === "expr" && value === tailordbCompareKnownDefaults.emptyExpression) return;
6550
+ return value;
6551
+ }
6552
+ if (Array.isArray(value)) return value.map((item, index) => normalizeTailorDBCompareValue(item, [...path, index])).filter((item) => item !== void 0);
6553
+ if (!isPlainObject(value)) return value;
6554
+ const normalizedEntries = Object.entries(value).map(([key, entryValue]) => [key, normalizeTailorDBCompareValue(entryValue, [...path, key])]).filter(([, entryValue]) => entryValue !== void 0);
6555
+ const normalizedObject = Object.fromEntries(normalizedEntries);
6556
+ if (path.at(-1) === "fields" && Object.keys(normalizedObject).length === 0) return;
6557
+ if (path.at(-1) === "disableGqlOperations" && areNormalizedEqual(normalizedObject, tailordbCompareKnownDefaults.disableGqlOperations)) return;
6558
+ return normalizedObject;
6559
+ }
6560
+ function matchesNumericStringPath(path) {
6561
+ const pathKey = path.map((segment) => String(segment)).join(".");
6562
+ return [...tailordbCompareKnownDefaults.numericStringPaths].some((pattern) => {
6563
+ const patternParts = pattern.split(".");
6564
+ const pathParts = pathKey.split(".");
6565
+ if (patternParts.length !== pathParts.length) return false;
6566
+ return patternParts.every((part, index) => part === "*" || part === pathParts[index]);
6567
+ });
6568
+ }
6569
+ function isNumericLikeValue(value) {
6570
+ return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
6571
+ }
6034
6572
  /**
6035
6573
  * Generate a TailorDB type manifest from parsed type
6036
6574
  * @param {TailorDBType} type - Parsed TailorDB type
@@ -6267,7 +6805,7 @@ function protoOperand(operand) {
6267
6805
  value: fromJson(ValueSchema, operand)
6268
6806
  } };
6269
6807
  }
6270
- async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices) {
6808
+ async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
6271
6809
  const changeSet = createChangeSet("TailorDB gqlPermissions");
6272
6810
  const fetchGqlPermissions = (namespaceName) => {
6273
6811
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -6295,14 +6833,17 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6295
6833
  for (const typeName of Object.keys(types)) {
6296
6834
  const gqlPermission = types[typeName].permissions.gql;
6297
6835
  if (!gqlPermission) continue;
6836
+ const desiredPermission = protoGqlPermission(gqlPermission);
6837
+ const existingPermission = existingGqlPermissions.find((entry) => entry.typeName === typeName);
6298
6838
  if (existingNameSet.has(typeName)) {
6299
- changeSet.updates.push({
6839
+ if (!forceApplyAll && existingPermission && areNormalizedEqual(normalizeComparableGqlPermission(existingPermission.permission), normalizeComparableGqlPermission(desiredPermission))) changeSet.unchanged.push({ name: typeName });
6840
+ else changeSet.updates.push({
6300
6841
  name: typeName,
6301
6842
  request: {
6302
6843
  workspaceId,
6303
6844
  namespaceName: tailordb.namespace,
6304
6845
  typeName,
6305
- permission: protoGqlPermission(gqlPermission)
6846
+ permission: desiredPermission
6306
6847
  }
6307
6848
  });
6308
6849
  existingNameSet.delete(typeName);
@@ -6312,7 +6853,7 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6312
6853
  workspaceId,
6313
6854
  namespaceName: tailordb.namespace,
6314
6855
  typeName,
6315
- permission: protoGqlPermission(gqlPermission)
6856
+ permission: desiredPermission
6316
6857
  }
6317
6858
  });
6318
6859
  }
@@ -6339,6 +6880,12 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
6339
6880
  });
6340
6881
  return changeSet;
6341
6882
  }
6883
+ function normalizeComparableGqlPermission(permission) {
6884
+ return { policies: (normalizeProtoConfig(permission)?.policies ?? []).map((policy) => ({
6885
+ ...policy,
6886
+ actions: [...policy.actions ?? []].sort((left, right) => left - right)
6887
+ })) };
6888
+ }
6342
6889
  function protoGqlPermission(permission) {
6343
6890
  return { policies: permission.map((policy) => protoGqlPolicy(policy)) };
6344
6891
  }
@@ -6503,7 +7050,7 @@ function formatMigrationCheckResults(results) {
6503
7050
  async function applyWorkflow(client, result, phase = "create-update") {
6504
7051
  const { changeSet, appName } = result;
6505
7052
  if (phase === "create-update") {
6506
- const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName);
7053
+ const jobFunctionVersions = await registerJobFunctions(client, changeSet, appName, result.unchangedWorkflowJobNames);
6507
7054
  await Promise.all([...changeSet.creates.map(async (create) => {
6508
7055
  const filteredVersions = filterJobFunctionVersions(jobFunctionVersions, create.usedJobNames);
6509
7056
  await client.createWorkflow({
@@ -6549,14 +7096,16 @@ function filterJobFunctionVersions(allVersions, usedJobNames) {
6549
7096
  * @param client - Operator client instance
6550
7097
  * @param changeSet - Workflow change set
6551
7098
  * @param appName - Application name
7099
+ * @param unchangedWorkflowJobNames - Job function names used by unchanged workflows
6552
7100
  * @returns Map of job function names to versions
6553
7101
  */
6554
- async function registerJobFunctions(client, changeSet, appName) {
7102
+ async function registerJobFunctions(client, changeSet, appName, unchangedWorkflowJobNames = /* @__PURE__ */ new Set()) {
6555
7103
  const jobFunctionVersions = {};
6556
- const firstWorkflow = changeSet.creates[0] || changeSet.updates[0];
7104
+ const firstWorkflow = changeSet.creates[0] || changeSet.updates[0] || changeSet.deletes[0];
6557
7105
  if (!firstWorkflow) return jobFunctionVersions;
6558
7106
  const { workspaceId } = firstWorkflow;
6559
7107
  const allUsedJobNames = /* @__PURE__ */ new Set();
7108
+ unchangedWorkflowJobNames.forEach((jobName) => allUsedJobNames.add(jobName));
6560
7109
  for (const item of [...changeSet.creates, ...changeSet.updates]) for (const jobName of item.usedJobNames) allUsedJobNames.add(jobName);
6561
7110
  const existingJobFunctions = await fetchAll(async (pageToken, maxPageSize) => {
6562
7111
  const response = await client.listWorkflowJobFunctions({
@@ -6567,23 +7116,25 @@ async function registerJobFunctions(client, changeSet, appName) {
6567
7116
  return [response.jobFunctions.map((j) => j.name), response.nextPageToken];
6568
7117
  });
6569
7118
  const existingJobNamesSet = new Set(existingJobFunctions);
6570
- const results = await Promise.all(Array.from(allUsedJobNames).map(async (jobName) => {
6571
- const response = existingJobNamesSet.has(jobName) ? await client.updateWorkflowJobFunction({
6572
- workspaceId,
6573
- jobFunctionName: jobName,
6574
- scriptRef: workflowJobFunctionName(jobName)
6575
- }) : await client.createWorkflowJobFunction({
6576
- workspaceId,
6577
- jobFunctionName: jobName,
6578
- scriptRef: workflowJobFunctionName(jobName)
6579
- });
6580
- await client.setMetadata(await buildMetaRequest(jobFunctionTrn(workspaceId, jobName), appName));
6581
- return {
6582
- jobName,
6583
- version: response.jobFunction?.version
6584
- };
6585
- }));
6586
- for (const { jobName, version } of results) if (version) jobFunctionVersions[jobName] = version;
7119
+ if (changeSet.creates.length > 0 || changeSet.updates.length > 0) {
7120
+ const results = await Promise.all(Array.from(allUsedJobNames).map(async (jobName) => {
7121
+ const response = existingJobNamesSet.has(jobName) ? await client.updateWorkflowJobFunction({
7122
+ workspaceId,
7123
+ jobFunctionName: jobName,
7124
+ scriptRef: workflowJobFunctionName(jobName)
7125
+ }) : await client.createWorkflowJobFunction({
7126
+ workspaceId,
7127
+ jobFunctionName: jobName,
7128
+ scriptRef: workflowJobFunctionName(jobName)
7129
+ });
7130
+ await client.setMetadata(await buildMetaRequest(jobFunctionTrn(workspaceId, jobName), appName));
7131
+ return {
7132
+ jobName,
7133
+ version: response.jobFunction?.version
7134
+ };
7135
+ }));
7136
+ for (const { jobName, version } of results) if (version) jobFunctionVersions[jobName] = version;
7137
+ }
6587
7138
  const unusedJobFunctions = existingJobFunctions.filter((jobName) => !allUsedJobNames.has(jobName));
6588
7139
  await Promise.all(unusedJobFunctions.map(async (jobName) => {
6589
7140
  const { metadata } = await client.getMetadata({ trn: jobFunctionTrn(workspaceId, jobName) });
@@ -6611,7 +7162,7 @@ function toRetryPolicy(policy) {
6611
7162
  backoffMultiplier: policy.backoffMultiplier
6612
7163
  };
6613
7164
  }
6614
- function workflowTrn(workspaceId, name) {
7165
+ function workflowTrn$1(workspaceId, name) {
6615
7166
  return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
6616
7167
  }
6617
7168
  function jobFunctionTrn(workspaceId, name) {
@@ -6624,35 +7175,35 @@ function jobFunctionTrn(workspaceId, name) {
6624
7175
  * @param appName - Application name
6625
7176
  * @param workflows - Parsed workflows
6626
7177
  * @param mainJobDeps - Main job dependencies by workflow
7178
+ * @param unchangedJobFunctions - Job functions already proven unchanged by function registry plan
6627
7179
  * @returns Planned workflow changes
6628
7180
  */
6629
- async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps) {
7181
+ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps, unchangedJobFunctions = /* @__PURE__ */ new Set()) {
6630
7182
  const changeSet = createChangeSet("Workflows");
6631
7183
  const conflicts = [];
6632
7184
  const unmanaged = [];
6633
7185
  const resourceOwners = /* @__PURE__ */ new Set();
7186
+ const unchangedWorkflowJobNames = /* @__PURE__ */ new Set();
6634
7187
  const withoutLabel = await fetchAll(async (pageToken, maxPageSize) => {
6635
7188
  const response = await client.listWorkflows({
6636
7189
  workspaceId,
6637
7190
  pageToken,
6638
7191
  pageSize: maxPageSize
6639
7192
  });
6640
- return [response.workflows.map((w) => ({
6641
- id: w.id,
6642
- name: w.name
6643
- })), response.nextPageToken];
7193
+ return [response.workflows, response.nextPageToken];
6644
7194
  });
6645
7195
  const existingWorkflows = {};
6646
7196
  await Promise.all(withoutLabel.map(async (resource) => {
6647
- const { metadata } = await client.getMetadata({ trn: workflowTrn(workspaceId, resource.name) });
7197
+ const { metadata } = await client.getMetadata({ trn: workflowTrn$1(workspaceId, resource.name) });
6648
7198
  existingWorkflows[resource.name] = {
6649
7199
  resource,
6650
- label: metadata?.labels[sdkNameLabelKey]
7200
+ label: metadata?.labels[sdkNameLabelKey],
7201
+ allLabels: metadata?.labels
6651
7202
  };
6652
7203
  }));
6653
7204
  for (const workflow of Object.values(workflows)) {
6654
7205
  const existing = existingWorkflows[workflow.name];
6655
- const metaRequest = await buildMetaRequest(workflowTrn(workspaceId, workflow.name), appName);
7206
+ const metaRequest = await buildMetaRequest(workflowTrn$1(workspaceId, workflow.name), appName);
6656
7207
  const usedJobNames = mainJobDeps[workflow.mainJob.name];
6657
7208
  if (!usedJobNames) throw new Error(`Job "${workflow.mainJob.name}" (mainJob of workflow "${workflow.name}") was not found.\n\nPossible causes:\n - The job is not exported as a named export\n - The file containing the job is not included in workflow.files glob pattern\n\nSolution:\n export const ${workflow.mainJob.name} = createWorkflowJob({ name: "${workflow.mainJob.name}", ... })`);
6658
7209
  if (existing) {
@@ -6665,7 +7216,10 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6665
7216
  resourceName: workflow.name,
6666
7217
  currentOwner: existing.label
6667
7218
  });
6668
- changeSet.updates.push({
7219
+ if (existing.label === appName && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && canTreatWorkflowAsUnchanged(existing.resource, workflow, usedJobNames, unchangedJobFunctions)) {
7220
+ changeSet.unchanged.push({ name: workflow.name });
7221
+ for (const jobName of usedJobNames) unchangedWorkflowJobNames.add(jobName);
7222
+ } else changeSet.updates.push({
6669
7223
  name: workflow.name,
6670
7224
  workspaceId,
6671
7225
  workflow,
@@ -6696,12 +7250,158 @@ async function planWorkflow(client, workspaceId, appName, workflows, mainJobDeps
6696
7250
  conflicts,
6697
7251
  unmanaged,
6698
7252
  resourceOwners,
6699
- appName
7253
+ appName,
7254
+ unchangedWorkflowJobNames
7255
+ };
7256
+ }
7257
+ function canTreatWorkflowAsUnchanged(existing, workflow, usedJobNames, unchangedJobFunctions) {
7258
+ if (!usedJobNames.every((jobName) => unchangedJobFunctions.has(jobName))) return false;
7259
+ return areWorkflowsEqual(existing, workflow, usedJobNames);
7260
+ }
7261
+ function areWorkflowsEqual(existing, workflow, usedJobNames) {
7262
+ return existing.mainJobFunctionName === workflow.mainJob.name && areNormalizedEqual(normalizeComparableExistingWorkflowRetryPolicy(existing.retryPolicy), normalizeComparableWorkflowRetryPolicy(workflow.retryPolicy)) && areNormalizedEqual(normalizeComparableWorkflowJobNames(existing.jobFunctions), normalizeComparableWorkflowJobNames(usedJobNames));
7263
+ }
7264
+ function normalizeComparableExistingWorkflowRetryPolicy(policy) {
7265
+ if (!policy) return;
7266
+ return normalizeRetryPolicyForCompare({
7267
+ maxRetries: policy.maxRetries ?? 0,
7268
+ backoffMultiplier: policy.backoffMultiplier ?? 0,
7269
+ initialBackoff: {
7270
+ seconds: policy.initialBackoff?.seconds ?? 0n,
7271
+ nanos: policy.initialBackoff?.nanos ?? 0
7272
+ },
7273
+ maxBackoff: {
7274
+ seconds: policy.maxBackoff?.seconds ?? 0n,
7275
+ nanos: policy.maxBackoff?.nanos ?? 0
7276
+ }
7277
+ });
7278
+ }
7279
+ function normalizeComparableWorkflowRetryPolicy(policy) {
7280
+ if (!policy) return;
7281
+ return normalizeRetryPolicyForCompare({
7282
+ maxRetries: policy.maxRetries,
7283
+ backoffMultiplier: policy.backoffMultiplier,
7284
+ initialBackoff: parseDurationToProto(policy.initialBackoff),
7285
+ maxBackoff: parseDurationToProto(policy.maxBackoff)
7286
+ });
7287
+ }
7288
+ function normalizeComparableWorkflowJobNames(jobFunctions) {
7289
+ return Array.isArray(jobFunctions) ? [...jobFunctions].sort() : Object.keys(jobFunctions ?? {}).sort();
7290
+ }
7291
+ function normalizeRetryPolicyForCompare(policy) {
7292
+ return {
7293
+ maxRetries: policy.maxRetries,
7294
+ backoffMultiplier: policy.backoffMultiplier,
7295
+ initialBackoff: {
7296
+ seconds: String(policy.initialBackoff.seconds),
7297
+ nanos: policy.initialBackoff.nanos
7298
+ },
7299
+ maxBackoff: {
7300
+ seconds: String(policy.maxBackoff.seconds),
7301
+ nanos: policy.maxBackoff.nanos
7302
+ }
6700
7303
  };
6701
7304
  }
6702
7305
 
6703
7306
  //#endregion
6704
7307
  //#region src/cli/commands/apply/apply.ts
7308
+ function applicationTrn(workspaceId, name) {
7309
+ return `trn:v1:workspace:${workspaceId}:application:${name}`;
7310
+ }
7311
+ function functionRegistryTrn(workspaceId, name) {
7312
+ return `trn:v1:workspace:${workspaceId}:function_registry:${name}`;
7313
+ }
7314
+ function pipelineTrn(workspaceId, name) {
7315
+ return `trn:v1:workspace:${workspaceId}:pipeline:${name}`;
7316
+ }
7317
+ function idpTrn(workspaceId, name) {
7318
+ return `trn:v1:workspace:${workspaceId}:idp:${name}`;
7319
+ }
7320
+ function authTrn(workspaceId, name) {
7321
+ return `trn:v1:workspace:${workspaceId}:auth:${name}`;
7322
+ }
7323
+ function executorTrn(workspaceId, name) {
7324
+ return `trn:v1:workspace:${workspaceId}:executor:${name}`;
7325
+ }
7326
+ function workflowTrn(workspaceId, name) {
7327
+ return `trn:v1:workspace:${workspaceId}:workflow:${name}`;
7328
+ }
7329
+ function staticWebsiteTrn(workspaceId, name) {
7330
+ return `trn:v1:workspace:${workspaceId}:staticwebsite:${name}`;
7331
+ }
7332
+ function tailorDBTrn(workspaceId, name) {
7333
+ return `trn:v1:workspace:${workspaceId}:tailordb:${name}`;
7334
+ }
7335
+ function vaultTrn(workspaceId, name) {
7336
+ return `trn:v1:workspace:${workspaceId}:vault:${name}`;
7337
+ }
7338
+ async function shouldForceApplyAll(client, workspaceId, application, functionEntries) {
7339
+ const desiredLabels = (await buildMetaRequest(applicationTrn(workspaceId, application.name), application.name)).labels;
7340
+ const candidateTrns = /* @__PURE__ */ new Set();
7341
+ if (application.subgraphs.length > 0) candidateTrns.add(applicationTrn(workspaceId, application.name));
7342
+ application.staticWebsiteServices.forEach((website) => {
7343
+ candidateTrns.add(staticWebsiteTrn(workspaceId, website.name));
7344
+ });
7345
+ application.resolverServices.forEach((pipeline) => {
7346
+ candidateTrns.add(pipelineTrn(workspaceId, pipeline.namespace));
7347
+ });
7348
+ application.idpServices.forEach((idp) => {
7349
+ candidateTrns.add(idpTrn(workspaceId, idp.name));
7350
+ });
7351
+ if (application.authService) candidateTrns.add(authTrn(workspaceId, application.authService.config.name));
7352
+ Object.values(application.executorService?.executors ?? {}).forEach((executor) => {
7353
+ candidateTrns.add(executorTrn(workspaceId, executor.name));
7354
+ });
7355
+ Object.values(application.workflowService?.workflows ?? {}).forEach((workflow) => {
7356
+ candidateTrns.add(workflowTrn(workspaceId, workflow.name));
7357
+ });
7358
+ application.tailorDBServices.forEach((service) => {
7359
+ candidateTrns.add(tailorDBTrn(workspaceId, service.namespace));
7360
+ });
7361
+ application.secrets.forEach((vault) => {
7362
+ candidateTrns.add(vaultTrn(workspaceId, vault.vaultName));
7363
+ });
7364
+ functionEntries.forEach((entry) => {
7365
+ candidateTrns.add(functionRegistryTrn(workspaceId, entry.name));
7366
+ });
7367
+ for (const trn of candidateTrns) try {
7368
+ const { metadata } = await client.getMetadata({ trn });
7369
+ if (metadata?.labels?.["sdk-name"] !== application.name) continue;
7370
+ if (!hasMatchingSdkVersion(metadata.labels, desiredLabels)) return true;
7371
+ } catch (error) {
7372
+ if (error instanceof ConnectError && error.code === Code.NotFound) continue;
7373
+ throw error;
7374
+ }
7375
+ return false;
7376
+ }
7377
+ function printPlanSummary(results) {
7378
+ const summary = summarizeChangeSets([
7379
+ results.functionRegistry.changeSet,
7380
+ results.tailorDB.changeSet.service,
7381
+ results.tailorDB.changeSet.type,
7382
+ results.tailorDB.changeSet.gqlPermission,
7383
+ results.staticWebsite.changeSet,
7384
+ results.idp.changeSet.service,
7385
+ results.idp.changeSet.client,
7386
+ results.auth.changeSet.service,
7387
+ results.auth.changeSet.idpConfig,
7388
+ results.auth.changeSet.userProfileConfig,
7389
+ results.auth.changeSet.tenantConfig,
7390
+ results.auth.changeSet.machineUser,
7391
+ results.auth.changeSet.oauth2Client,
7392
+ results.auth.changeSet.authHook,
7393
+ results.auth.changeSet.scim,
7394
+ results.auth.changeSet.scimResource,
7395
+ results.pipeline.changeSet.service,
7396
+ results.pipeline.changeSet.resolver,
7397
+ results.app,
7398
+ results.executor.changeSet,
7399
+ results.workflow.changeSet,
7400
+ results.secretManager.vaultChangeSet,
7401
+ results.secretManager.secretChangeSet
7402
+ ]);
7403
+ logger.log(formatPlanSummary(summary));
7404
+ }
6705
7405
  /**
6706
7406
  * Apply the configured application to the Tailor platform.
6707
7407
  * @param options - Options for apply execution
@@ -6775,9 +7475,10 @@ async function apply(options) {
6775
7475
  rootSpan.setAttribute("app.name", application.name);
6776
7476
  rootSpan.setAttribute("workspace.id", workspaceId);
6777
7477
  const workflowService = application.workflowService;
6778
- const functionEntries = collectFunctionEntries(application, workflowService?.jobs ?? [], bundledScripts);
7478
+ const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
6779
7479
  const dryRun = options?.dryRun ?? false;
6780
7480
  const yes = options?.yes ?? false;
7481
+ const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
6781
7482
  const { functionRegistry, tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager } = await withSpan("plan", async () => {
6782
7483
  const ctx = {
6783
7484
  client,
@@ -6785,10 +7486,12 @@ async function apply(options) {
6785
7486
  application,
6786
7487
  forRemoval: false,
6787
7488
  config,
6788
- noSchemaCheck: options?.noSchemaCheck
7489
+ noSchemaCheck: options?.noSchemaCheck,
7490
+ forceApplyAll
6789
7491
  };
6790
- const [functionRegistry, tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
6791
- withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries)),
7492
+ const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, functionEntries));
7493
+ const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.map((entry) => entry.name).filter((name) => name.startsWith("workflow--")).map((name) => name.slice(10)));
7494
+ const [tailorDB, staticWebsite, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
6792
7495
  withSpan("plan.tailorDB", () => planTailorDB(ctx)),
6793
7496
  withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
6794
7497
  withSpan("plan.idp", () => planIdP(ctx)),
@@ -6796,7 +7499,7 @@ async function apply(options) {
6796
7499
  withSpan("plan.pipeline", () => planPipeline(ctx)),
6797
7500
  withSpan("plan.application", () => planApplication(ctx)),
6798
7501
  withSpan("plan.executor", () => planExecutor(ctx)),
6799
- withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {})),
7502
+ withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
6800
7503
  withSpan("plan.secretManager", () => planSecretManager(ctx))
6801
7504
  ]);
6802
7505
  return {
@@ -6882,6 +7585,18 @@ async function apply(options) {
6882
7585
  }
6883
7586
  });
6884
7587
  });
7588
+ printPlanSummary({
7589
+ functionRegistry,
7590
+ tailorDB,
7591
+ staticWebsite,
7592
+ idp,
7593
+ auth,
7594
+ pipeline,
7595
+ app,
7596
+ executor,
7597
+ workflow,
7598
+ secretManager
7599
+ });
6885
7600
  if (dryRun) {
6886
7601
  logger.info("Dry run enabled. No changes applied.");
6887
7602
  return;
@@ -7214,7 +7929,7 @@ async function getExecutor(options) {
7214
7929
  try {
7215
7930
  return toExecutorInfo(await resolveExecutor(client, workspaceId, name));
7216
7931
  } catch (error) {
7217
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${name}' not found.`);
7932
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${name}' not found.`, { cause: error });
7218
7933
  throw error;
7219
7934
  }
7220
7935
  }
@@ -7690,7 +8405,7 @@ async function getWorkflow(options) {
7690
8405
  try {
7691
8406
  return toWorkflowInfo(await resolveWorkflow(client, workspaceId, name));
7692
8407
  } catch (error) {
7693
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Workflow '${name}' not found.`);
8408
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Workflow '${name}' not found.`, { cause: error });
7694
8409
  throw error;
7695
8410
  }
7696
8411
  }
@@ -7821,7 +8536,7 @@ async function startWorkflowCore(options) {
7821
8536
  })
7822
8537
  };
7823
8538
  } catch (error) {
7824
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Workflow '${workflowName}' not found.`);
8539
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Workflow '${workflowName}' not found.`, { cause: error });
7825
8540
  throw error;
7826
8541
  }
7827
8542
  }
@@ -7949,7 +8664,7 @@ async function listExecutorJobs(options) {
7949
8664
  });
7950
8665
  return jobs.map(toExecutorJobListInfo);
7951
8666
  } catch (error) {
7952
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${executorName}' not found.`);
8667
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${executorName}' not found.`, { cause: error });
7953
8668
  throw error;
7954
8669
  }
7955
8670
  }
@@ -7989,7 +8704,7 @@ async function getExecutorJob(options) {
7989
8704
  }
7990
8705
  return jobInfo;
7991
8706
  } catch (error) {
7992
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Job '${options.jobId}' not found for executor '${executorName}'.`);
8707
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Job '${options.jobId}' not found for executor '${executorName}'.`, { cause: error });
7993
8708
  throw error;
7994
8709
  }
7995
8710
  }
@@ -8378,8 +9093,8 @@ async function triggerExecutorByName(options) {
8378
9093
  payload: options.payload
8379
9094
  })).jobId };
8380
9095
  } catch (error) {
8381
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${options.executorName}' not found.`);
8382
- if (error instanceof ConnectError && error.code === Code.InvalidArgument) throw new Error(`Invalid argument: ${error.message}`);
9096
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Executor '${options.executorName}' not found.`, { cause: error });
9097
+ if (error instanceof ConnectError && error.code === Code.InvalidArgument) throw new Error(`Invalid argument: ${error.message}`, { cause: error });
8383
9098
  throw error;
8384
9099
  }
8385
9100
  }
@@ -9665,7 +10380,7 @@ async function getOAuth2Client(options) {
9665
10380
  });
9666
10381
  return toOAuth2ClientCredentials(oauth2Client);
9667
10382
  } catch (error) {
9668
- if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`OAuth2 client '${options.name}' not found.`);
10383
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`OAuth2 client '${options.name}' not found.`, { cause: error });
9669
10384
  throw error;
9670
10385
  }
9671
10386
  }
@@ -10965,7 +11680,7 @@ async function generate(options) {
10965
11680
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
10966
11681
  let pluginManager;
10967
11682
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
10968
- const { defineApplication } = await import("./application-DHifc-oA.mjs");
11683
+ const { defineApplication } = await import("./application-ChVyhwe-.mjs");
10969
11684
  const application = defineApplication({
10970
11685
  config,
10971
11686
  pluginManager
@@ -11383,8 +12098,8 @@ async function resumeWorkflow(options) {
11383
12098
  };
11384
12099
  } catch (error) {
11385
12100
  if (error instanceof ConnectError) {
11386
- if (error.code === Code.NotFound) throw new Error(`Execution '${options.executionId}' not found.`);
11387
- if (error.code === Code.FailedPrecondition) throw new Error(`Execution '${options.executionId}' is not in a resumable state.`);
12101
+ if (error.code === Code.NotFound) throw new Error(`Execution '${options.executionId}' not found.`, { cause: error });
12102
+ if (error.code === Code.FailedPrecondition) throw new Error(`Execution '${options.executionId}' is not in a resumable state.`, { cause: error });
11388
12103
  }
11389
12104
  throw error;
11390
12105
  }
@@ -12476,7 +13191,7 @@ function extractTypeNamesFromSql(query) {
12476
13191
  statements = parse(query);
12477
13192
  } catch (error) {
12478
13193
  const message = error instanceof Error ? error.message : String(error);
12479
- throw new Error(`SQL parse error: ${message}\nIf your table name is a reserved keyword (e.g. User), wrap it in double quotes: SELECT * FROM "User"`);
13194
+ throw new Error(`SQL parse error: ${message}\nIf your table name is a reserved keyword (e.g. User), wrap it in double quotes: SELECT * FROM "User"`, { cause: error });
12480
13195
  }
12481
13196
  const typeNames = /* @__PURE__ */ new Set();
12482
13197
  const visitor = astVisitor((mapper) => ({ tableRef: (tableRef) => {
@@ -12714,7 +13429,7 @@ async function resolveEditedQueryInput(engine) {
12714
13429
  try {
12715
13430
  await openInEditor(filePath, editor);
12716
13431
  } catch (error) {
12717
- throw new Error(`Failed to open query editor "${editor}": ${error instanceof Error ? error.message : String(error)}`);
13432
+ throw new Error(`Failed to open query editor "${editor}": ${error instanceof Error ? error.message : String(error)}`, { cause: error });
12718
13433
  }
12719
13434
  const editedQuery = await fs.readFile(filePath, "utf-8");
12720
13435
  if (editedQuery.trim().length === 0 || editedQuery === initialQuery) return { mode: "abort" };
@@ -13170,4 +13885,4 @@ function isDeno() {
13170
13885
 
13171
13886
  //#endregion
13172
13887
  export { getFolder as $, getNextMigrationNumber as $t, listWorkflows as A, functionExecutionStatusToString as At, updateCommand$1 as B, DB_TYPES_FILE_NAME as Bt, listApps as C, startCommand as Ct, resumeCommand as D, executionsCommand as Dt, healthCommand as E, getWorkflow as Et, show as F, executeScript as Ft, listOrganizations as G, compareLocalTypesWithSnapshot as Gt, organizationTree as H, INITIAL_SCHEMA_NUMBER as Ht, showCommand as I, waitForExecution$1 as It, updateCommand$2 as J, formatMigrationNumber as Jt, getCommand$1 as K, compareSnapshots as Kt, logBetaWarning as L, MIGRATION_LABEL_KEY as Lt, truncateCommand as M, getCommand$5 as Mt, generate as N, getExecutor as Nt, resumeWorkflow as O, getWorkflowExecution as Ot, generateCommand as P, apply as Pt, getCommand$2 as Q, getMigrationFiles as Qt, remove as R, parseMigrationLabelNumber as Rt, createWorkspace as S, watchExecutorJob as St, getAppHealth as T, getCommand$4 as Tt, treeCommand as U, MIGRATE_FILE_NAME as Ut, updateOrganization as V, DIFF_FILE_NAME as Vt, listCommand$4 as W, SCHEMA_FILE_NAME as Wt, listCommand$5 as X, getMigrationDirPath as Xt, updateFolder as Y, getLatestMigrationNumber as Yt, listFolders as Z, getMigrationFilePath as Zt, getCommand as _, isVerbose as _n, listCommand$8 as _t, updateCommand as a, hasChanges as an, listOAuth2Clients as at, deleteWorkspace as b, jobsCommand as bt, removeUser as c, sdkNameLabelKey as cn, getMachineUserToken as ct, inviteCommand as d, apiCall as dn, listMachineUsers as dt, isValidMigrationNumber as en, deleteCommand$1 as et, inviteUser as f, apiCommand as fn, generate$1 as ft, listWorkspaces as g, deploymentArgs as gn, triggerExecutor as gt, listCommand$1 as h, confirmationArgs as hn, triggerCommand as ht, isCLIError as i, formatMigrationDiff as in, listCommand$6 as it, truncate as j, formatKeyValueTable as jt, listCommand$3 as k, listWorkflowExecutions as kt, listCommand as l, trnPrefix as ln, tokenCommand as lt, restoreWorkspace as m, commonArgs as mn, webhookCommand as mt, query as n, reconstructSnapshotFromMigrations as nn, createCommand$1 as nt, updateUser as o, getNamespacesWithMigrations as on, getCommand$3 as ot, restoreCommand as p, defineAppCommand as pn, listWebhookExecutors as pt, getOrganization as q, createSnapshotFromLocalTypes as qt, queryCommand as r, formatDiffSummary as rn, createFolder as rt, removeCommand as s, prompt as sn, getOAuth2Client as st, isNativeTypeScriptRuntime as t, loadDiff as tn, deleteFolder as tt, listUsers as u, generateUserTypes as un, listCommand$7 as ut, getWorkspace as v, workspaceArgs as vn, listExecutors as vt, listCommand$2 as w, startWorkflow as wt, createCommand as x, listExecutorJobs as xt, deleteCommand as y, getExecutorJob as yt, removeCommand$1 as z, bundleMigrationScript as zt };
13173
- //# sourceMappingURL=runtime-DbX_UxlC.mjs.map
13888
+ //# sourceMappingURL=runtime-3P9JFpe9.mjs.map