@tailor-platform/sdk 1.48.0 → 1.50.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 (51) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +1 -1
  3. package/dist/{application-DUENhx4Y.mjs → application-CZMzt9jL.mjs} +3 -2
  4. package/dist/application-CZMzt9jL.mjs.map +1 -0
  5. package/dist/application-v_E2W-Fz.mjs +4 -0
  6. package/dist/brand-D-d15jx3.mjs.map +1 -1
  7. package/dist/cli/index.mjs +137 -25
  8. package/dist/cli/index.mjs.map +1 -1
  9. package/dist/cli/lib.d.mts +1409 -1373
  10. package/dist/cli/lib.mjs +4 -4
  11. package/dist/cli/lib.mjs.map +1 -1
  12. package/dist/cli/skills.mjs.map +1 -1
  13. package/dist/client-_kHh0Pip.mjs.map +1 -1
  14. package/dist/configure/index.mjs.map +1 -1
  15. package/dist/crashreport-CvmdFs4i.mjs.map +1 -1
  16. package/dist/enum-constants-C3KSpsYj.mjs.map +1 -1
  17. package/dist/errors-pMPXghkO.mjs.map +1 -1
  18. package/dist/field-DLSIuMTu.mjs.map +1 -1
  19. package/dist/file-utils-DjNi_3U_.mjs.map +1 -1
  20. package/dist/interceptor-DTNS0EtF.mjs.map +1 -1
  21. package/dist/job-M3Avv_SV.mjs.map +1 -1
  22. package/dist/kysely/index.mjs.map +1 -1
  23. package/dist/kysely-type-B8aRz_oC.mjs.map +1 -1
  24. package/dist/logger-DTNAMYGy.mjs.map +1 -1
  25. package/dist/mock-BfL09ULZ.mjs.map +1 -1
  26. package/dist/multiline-e3IpANmS.mjs.map +1 -1
  27. package/dist/package-json-6Px8bDpG.mjs.map +1 -1
  28. package/dist/plugin/index.mjs.map +1 -1
  29. package/dist/repl-editor-jZ493eQI.mjs.map +1 -1
  30. package/dist/{runtime-CNg0w27y.mjs → runtime-B2K6JW7V.mjs} +543 -164
  31. package/dist/runtime-B2K6JW7V.mjs.map +1 -0
  32. package/dist/schema-C5QjYEc-.mjs.map +1 -1
  33. package/dist/secret-file-BHpxGyNf.mjs.map +1 -1
  34. package/dist/seed/index.mjs.map +1 -1
  35. package/dist/seed-DjfAn0BC.mjs.map +1 -1
  36. package/dist/service-DCgJxdg1.mjs.map +1 -1
  37. package/dist/telemetry-C1Y56L5E.mjs.map +1 -1
  38. package/dist/types-sir9UPht.mjs.map +1 -1
  39. package/dist/utils/test/index.mjs.map +1 -1
  40. package/dist/vitest/environment.mjs.map +1 -1
  41. package/dist/vitest/index.mjs.map +1 -1
  42. package/dist/vitest/setup.mjs.map +1 -1
  43. package/docs/cli/tailordb.md +50 -0
  44. package/docs/cli/workspace.md +20 -17
  45. package/docs/cli-reference.md +1 -0
  46. package/docs/services/tailordb-migration.md +49 -31
  47. package/package.json +8 -8
  48. package/dist/application-BNkNt47b.mjs +0 -4
  49. package/dist/application-DUENhx4Y.mjs.map +0 -1
  50. package/dist/runtime-CNg0w27y.mjs.map +0 -1
  51. /package/{skills → agent-skills}/tailor-sdk/SKILL.md +0 -0
@@ -2,7 +2,7 @@
2
2
  import { t as db } from "./schema-C5QjYEc-.mjs";
3
3
  import { $ as FilterSchema, A as FunctionExecution_Status, B as AuthOAuth2Client_GrantType, C as TailorDBType_Permission_Operator, D as IdPLang, E as PipelineResolver_OperationType, F as AuthConnection_Type, H as AuthSCIMAttribute_Type, I as AuthHookPoint, J as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, K as TenantProviderConfig_TenantProviderType, L as AuthIDPConfig_AuthType, M as ExecutorJobStatus, N as ExecutorTargetType, O as IdPPermissionOperator, P as ExecutorTriggerType, Q as Condition_Operator, R as AuthInvokerSchema, S as TailorDBGQLPermission_Permit, T as TailorDBType_PermitAction, U as AuthSCIMAttribute_Uniqueness, V as AuthSCIMAttribute_Mutability, W as AuthSCIMConfig_AuthorizationType, X as Subgraph_ServiceType, Y as ApplicationSchemaUpdateAttemptStatus, Z as ConditionSchema, _ as WorkspacePlatformUserRole, a as fetchMachineUserToken, b as TailorDBGQLPermission_Action, d as initOperatorClient, et as PageDirection, g as OperatorService, h as userAgent, i as fetchAll, k as IdPPermissionPermit, m as resolveStaticWebsiteUrls, o as fetchPaged, p as platformBaseUrl, q as UserProfileProviderConfig_UserProfileProviderType, v as WorkflowExecution_Status, w as TailorDBType_Permission_Permit, x as TailorDBGQLPermission_Operator, y as WorkflowJobExecution_Status, z as AuthOAuth2Client_ClientType } from "./client-_kHh0Pip.mjs";
4
4
  import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-DTNAMYGy.mjs";
5
- import { C as loadConfigPath, O as writePlatformConfig, S as loadAccessToken, T as readPlatformConfig, _ as getDistDir, d as buildResolverOperationHookExpr, f as OAuth2ClientSchema, g as createBundleCache, h as loadFilesWithIgnores, m as stringifyFunction, n as generatePluginFilesIfNeeded, p as TailorDBTypeSchema, r as loadApplication, s as createExecutorService, t as defineApplication, u as buildExecutorArgsExpr, v as hashFile, w as loadWorkspaceId, y as loadConfig } from "./application-DUENhx4Y.mjs";
5
+ import { C as loadConfigPath, O as writePlatformConfig, S as loadAccessToken, T as readPlatformConfig, _ as getDistDir, d as buildResolverOperationHookExpr, f as OAuth2ClientSchema, g as createBundleCache, h as loadFilesWithIgnores, m as stringifyFunction, n as generatePluginFilesIfNeeded, p as TailorDBTypeSchema, r as loadApplication, s as createExecutorService, t as defineApplication, u as buildExecutorArgsExpr, v as hashFile, w as loadWorkspaceId, y as loadConfig } from "./application-CZMzt9jL.mjs";
6
6
  import { t as multiline } from "./multiline-e3IpANmS.mjs";
7
7
  import { t as readPackageJson } from "./package-json-6Px8bDpG.mjs";
8
8
  import { n as isCLIError, t as createCLIError } from "./errors-pMPXghkO.mjs";
@@ -267,6 +267,38 @@ function isVerbose() {
267
267
  */
268
268
  const defineAppCommand = createDefineCommand();
269
269
 
270
+ //#endregion
271
+ //#region src/cli/shared/readonly-guard.ts
272
+ /**
273
+ * Throw a CLIError if the active profile has `readonly: true`.
274
+ *
275
+ * Resolves the active profile in this order:
276
+ * 1. `opts.profile` (CLI flag)
277
+ * 2. `process.env.TAILOR_PLATFORM_PROFILE`
278
+ *
279
+ * If neither is set, no profile is in scope so the call is allowed. This is
280
+ * intentional: `TAILOR_PLATFORM_TOKEN` direct access (CI / machine user) and
281
+ * `--workspace-id` without a profile are out-of-band paths whose authorization
282
+ * is governed by the bearer token itself, not by the local profile flag.
283
+ *
284
+ * If the resolved profile cannot be found in the config, this function returns
285
+ * silently and lets downstream loaders surface the not-found error.
286
+ * @param opts - Options
287
+ * @param opts.profile - Optional explicit profile name from command args
288
+ */
289
+ async function assertWritable(opts) {
290
+ const profileName = opts?.profile || process.env.TAILOR_PLATFORM_PROFILE;
291
+ if (!profileName) return;
292
+ const profile = (await readPlatformConfig()).profiles[profileName];
293
+ if (!profile || profile.readonly !== true) return;
294
+ throw createCLIError({
295
+ code: "PROFILE_READONLY",
296
+ message: `Profile "${profileName}" is read-only.`,
297
+ details: "This profile blocks platform-state mutations (apply, create/update/delete, deploy, etc.). Application-data operations remain available because their permissions are governed by the machine user.",
298
+ suggestion: `Use a different profile, unset TAILOR_PLATFORM_PROFILE, or run 'tailor-sdk profile update ${profileName} --permission write'.`
299
+ });
300
+ }
301
+
270
302
  //#endregion
271
303
  //#region src/cli/commands/api/api-call.ts
272
304
  /**
@@ -568,6 +600,7 @@ Values already present in \`--body\` are never overridden. If a value cannot be
568
600
  })
569
601
  }).strict(),
570
602
  run: async (args) => {
603
+ await assertWritable({ profile: args.profile });
571
604
  const methodName = extractMethodName(args.endpoint);
572
605
  const method = getMethodDescriptor(methodName);
573
606
  const parsedBody = parseBodyAsObject(args.body);
@@ -5763,6 +5796,20 @@ function formatBreakingChanges(breakingChanges) {
5763
5796
  }
5764
5797
  return lines.join("\n");
5765
5798
  }
5799
+ /**
5800
+ * Format warning changes for display
5801
+ * @param {WarningChangeInfo[]} warnings - Warning changes to format
5802
+ * @returns {string} Formatted warning changes string
5803
+ */
5804
+ function formatWarnings(warnings) {
5805
+ if (warnings.length === 0) return "";
5806
+ const lines = ["Warning: data loss possible:", ""];
5807
+ for (const w of warnings) {
5808
+ const location = w.fieldName ? `${w.typeName}.${w.fieldName}` : w.typeName;
5809
+ lines.push(` - ${location}: ${w.reason}`);
5810
+ }
5811
+ return lines.join("\n");
5812
+ }
5766
5813
  const DIFF_CHANGE_LABELS = {
5767
5814
  type_added: "type(s) added",
5768
5815
  type_removed: "type(s) removed",
@@ -5823,15 +5870,37 @@ const MIGRATION_NUMBER_PATTERN = /^\d{4}$/;
5823
5870
  */
5824
5871
  const DEFAULT_DECIMAL_SCALE = 6;
5825
5872
  /**
5826
- * Resolve the effective scale of a field for comparison purposes.
5827
- * Decimal fields without an explicit scale are stored on the platform with the
5828
- * default scale, so we normalize unset values to the default to avoid false drift.
5829
- * @param {SnapshotFieldConfig} field - Field configuration
5830
- * @returns {number | undefined} Effective scale, or undefined for non-decimal fields without scale
5873
+ * Normalize a snapshot field in place so the snapshot becomes the canonical
5874
+ * form for comparison. Currently fills in the platform default decimal scale
5875
+ * when omitted, which avoids false drift between local schemas (where scale
5876
+ * may be omitted) and the platform (which always materializes a scale).
5877
+ * @param {SnapshotFieldConfig} field - Field configuration to normalize
5878
+ */
5879
+ function normalizeSnapshotField(field) {
5880
+ if (field.type === "decimal" && field.scale === void 0) field.scale = 6;
5881
+ if (field.fields) for (const nested of Object.values(field.fields)) normalizeSnapshotField(nested);
5882
+ }
5883
+ /**
5884
+ * Normalize a snapshot type in place to the canonical comparison shape.
5885
+ * Currently fills:
5886
+ * - `pluralForm` via inflection when missing (legacy snapshots written
5887
+ * before `pluralForm` became required may omit it)
5888
+ * - per-field `scale` defaults via {@link normalizeSnapshotField}
5889
+ *
5890
+ * Idempotent — safe to call multiple times on the same input.
5891
+ * @param {TailorDBSnapshotType} type - Snapshot type to normalize
5892
+ */
5893
+ function normalizeSnapshotType(type) {
5894
+ if (!type.pluralForm) type.pluralForm = inflection.pluralize(type.name);
5895
+ for (const field of Object.values(type.fields)) normalizeSnapshotField(field);
5896
+ }
5897
+ /**
5898
+ * Type guard: is the operand a field-reference (object) operand?
5899
+ * @param {SnapshotPermissionOperand} operand - Operand to test
5900
+ * @returns {boolean} True if operand is a field-ref (not a value operand)
5831
5901
  */
5832
- function getEffectiveScale(field) {
5833
- if (field.scale !== void 0) return field.scale;
5834
- if (field.type === "decimal") return 6;
5902
+ function isSnapshotFieldRefOperand(operand) {
5903
+ return typeof operand === "object" && operand !== null && !Array.isArray(operand);
5835
5904
  }
5836
5905
  /**
5837
5906
  * Validate that a migration number follows the expected format (4-digit number)
@@ -5922,6 +5991,7 @@ function createSnapshotFieldConfig(field) {
5922
5991
  config.fields = {};
5923
5992
  for (const [nestedName, nestedConfig] of Object.entries(field.config.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
5924
5993
  }
5994
+ normalizeSnapshotField(config);
5925
5995
  return config;
5926
5996
  }
5927
5997
  /**
@@ -5967,21 +6037,22 @@ function createSnapshotFieldConfigFromOperatorConfig(fieldConfig) {
5967
6037
  config.fields = {};
5968
6038
  for (const [nestedName, nestedConfig] of Object.entries(fieldConfig.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
5969
6039
  }
6040
+ normalizeSnapshotField(config);
5970
6041
  return config;
5971
6042
  }
5972
6043
  /**
5973
6044
  * Create a snapshot type from a parsed type
5974
6045
  * @param {TailorDBType} type - Parsed TailorDB type definition
5975
- * @returns {SnapshotType} Snapshot type configuration
6046
+ * @returns {TailorDBSnapshotType} Snapshot type configuration
5976
6047
  */
5977
6048
  function createSnapshotType(type) {
5978
6049
  const fields = {};
5979
6050
  for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = createSnapshotFieldConfig(field);
5980
6051
  const snapshotType = {
5981
6052
  name: type.name,
6053
+ pluralForm: type.pluralForm || inflection.pluralize(type.name),
5982
6054
  fields
5983
6055
  };
5984
- if (type.pluralForm) snapshotType.pluralForm = type.pluralForm;
5985
6056
  if (type.description) snapshotType.description = type.description;
5986
6057
  if (type.settings) {
5987
6058
  snapshotType.settings = {};
@@ -6078,7 +6149,9 @@ function createSnapshotFromLocalTypes(types, namespace) {
6078
6149
  */
6079
6150
  function loadSnapshot(filePath) {
6080
6151
  const content = fs$1.readFileSync(filePath, "utf-8");
6081
- return JSON.parse(content);
6152
+ const snapshot = JSON.parse(content);
6153
+ for (const type of Object.values(snapshot.types)) normalizeSnapshotType(type);
6154
+ return snapshot;
6082
6155
  }
6083
6156
  /**
6084
6157
  * Load a migration diff from a file
@@ -6087,7 +6160,13 @@ function loadSnapshot(filePath) {
6087
6160
  */
6088
6161
  function loadDiff(filePath) {
6089
6162
  const content = fs$1.readFileSync(filePath, "utf-8");
6090
- return JSON.parse(content);
6163
+ const parsed = JSON.parse(content);
6164
+ const warnings = parsed.warnings ?? [];
6165
+ return {
6166
+ ...parsed,
6167
+ warnings,
6168
+ hasWarnings: warnings.length > 0
6169
+ };
6091
6170
  }
6092
6171
  /**
6093
6172
  * Get all migration directories and their files, sorted by number
@@ -6350,7 +6429,7 @@ function areFieldsDifferent(oldField, newField) {
6350
6429
  if (oldSerial.maxValue !== newSerial.maxValue) return true;
6351
6430
  if ((oldSerial.format ?? "") !== (newSerial.format ?? "")) return true;
6352
6431
  }
6353
- if (getEffectiveScale(oldField) !== getEffectiveScale(newField)) return true;
6432
+ if (oldField.scale !== newField.scale) return true;
6354
6433
  const oldFields = oldField.fields ?? {};
6355
6434
  const newFields = newField.fields ?? {};
6356
6435
  const oldFieldNames = Object.keys(oldFields);
@@ -6428,10 +6507,17 @@ function isBreakingFieldChange(typeName, fieldName, oldField, newField) {
6428
6507
  }
6429
6508
  function addChange(ctx, change, oldField, newField) {
6430
6509
  ctx.changes.push(change);
6431
- if (change.fieldName) {
6432
- const breaking = isBreakingFieldChange(change.typeName, change.fieldName, oldField, newField);
6433
- if (breaking) ctx.breakingChanges.push(breaking);
6510
+ if (!change.fieldName) return;
6511
+ const breaking = isBreakingFieldChange(change.typeName, change.fieldName, oldField, newField);
6512
+ if (breaking) {
6513
+ ctx.breakingChanges.push(breaking);
6514
+ return;
6434
6515
  }
6516
+ if (change.kind === "field_removed") ctx.warnings.push({
6517
+ typeName: change.typeName,
6518
+ fieldName: change.fieldName,
6519
+ reason: "Field removed (existing data will be dropped in the post-migration phase)"
6520
+ });
6435
6521
  }
6436
6522
  function compareTypeFields(ctx, typeName, prevType, currType) {
6437
6523
  const prevFieldNames = new Set(Object.keys(prevType.fields));
@@ -6622,9 +6708,12 @@ function comparePermissions(ctx, typeName, oldRecordPerm, newRecordPerm, oldGqlP
6622
6708
  * @returns {MigrationDiff} Migration diff between snapshots
6623
6709
  */
6624
6710
  function compareSnapshots(previous, current) {
6711
+ for (const type of Object.values(previous.types)) normalizeSnapshotType(type);
6712
+ for (const type of Object.values(current.types)) normalizeSnapshotType(type);
6625
6713
  const ctx = {
6626
6714
  changes: [],
6627
- breakingChanges: []
6715
+ breakingChanges: [],
6716
+ warnings: []
6628
6717
  };
6629
6718
  const previousTypeNames = new Set(Object.keys(previous.types));
6630
6719
  const currentTypeNames = new Set(Object.keys(current.types));
@@ -6633,11 +6722,17 @@ function compareSnapshots(previous, current) {
6633
6722
  typeName,
6634
6723
  after: current.types[typeName]
6635
6724
  });
6636
- for (const typeName of previousTypeNames) if (!currentTypeNames.has(typeName)) ctx.changes.push({
6637
- kind: "type_removed",
6638
- typeName,
6639
- before: previous.types[typeName]
6640
- });
6725
+ for (const typeName of previousTypeNames) if (!currentTypeNames.has(typeName)) {
6726
+ ctx.changes.push({
6727
+ kind: "type_removed",
6728
+ typeName,
6729
+ before: previous.types[typeName]
6730
+ });
6731
+ ctx.warnings.push({
6732
+ typeName,
6733
+ reason: "Type removed (all records of this type will be dropped in the post-migration phase)"
6734
+ });
6735
+ }
6641
6736
  for (const typeName of currentTypeNames) {
6642
6737
  if (!previousTypeNames.has(typeName)) continue;
6643
6738
  const prevType = previous.types[typeName];
@@ -6656,18 +6751,29 @@ function compareSnapshots(previous, current) {
6656
6751
  changes: ctx.changes,
6657
6752
  hasBreakingChanges: ctx.breakingChanges.length > 0,
6658
6753
  breakingChanges: ctx.breakingChanges,
6754
+ hasWarnings: ctx.warnings.length > 0,
6755
+ warnings: ctx.warnings,
6659
6756
  requiresMigrationScript: ctx.breakingChanges.length > 0
6660
6757
  };
6661
6758
  }
6662
6759
  /**
6663
- * Compare local types with a snapshot and generate a diff
6760
+ * Compare a snapshot against canonical TailorDBSnapshotType-shaped local types.
6761
+ * Callers are expected to pre-convert TailorDBService.types to TailorDBSnapshotType via
6762
+ * `createSnapshotType`. As a safety net, `compareSnapshots` re-runs idempotent
6763
+ * normalization on both sides, so a caller that forgets will still get correct
6764
+ * comparisons (no silent false drift).
6664
6765
  * @param {SchemaSnapshot} snapshot - Schema snapshot to compare against
6665
- * @param {Record<string, TailorDBType>} localTypes - Local type definitions
6766
+ * @param {Record<string, TailorDBSnapshotType>} localTypes - Local snapshot-shaped types
6666
6767
  * @param {string} namespace - Namespace for comparison
6667
6768
  * @returns {MigrationDiff} Migration diff
6668
6769
  */
6669
6770
  function compareLocalTypesWithSnapshot(snapshot, localTypes, namespace) {
6670
- return compareSnapshots(snapshot, createSnapshotFromLocalTypes(localTypes, namespace));
6771
+ return compareSnapshots(snapshot, {
6772
+ version: 1,
6773
+ namespace,
6774
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6775
+ types: localTypes
6776
+ });
6671
6777
  }
6672
6778
  /**
6673
6779
  * Validate migration files in a directory
@@ -6774,6 +6880,7 @@ function convertRemoteFieldsToSnapshot(remoteType) {
6774
6880
  ...remoteField.serial.format && { format: remoteField.serial.format }
6775
6881
  };
6776
6882
  if (remoteField.scale !== void 0) config.scale = remoteField.scale;
6883
+ normalizeSnapshotField(config);
6777
6884
  fields[fieldName] = config;
6778
6885
  }
6779
6886
  return fields;
@@ -6818,9 +6925,7 @@ function compareFields(typeName, fieldName, remoteField, snapshotField) {
6818
6925
  const remoteVector = remoteField.vector ?? false;
6819
6926
  const snapshotVector = snapshotField.vector ?? false;
6820
6927
  if (remoteVector !== snapshotVector) differences.push(`vector: remote=${remoteVector}, expected=${snapshotVector}`);
6821
- const remoteScale = getEffectiveScale(remoteField);
6822
- const snapshotScale = getEffectiveScale(snapshotField);
6823
- if (remoteScale !== snapshotScale) differences.push(`scale: remote=${remoteScale}, expected=${snapshotScale}`);
6928
+ if (remoteField.scale !== snapshotField.scale) differences.push(`scale: remote=${remoteField.scale}, expected=${snapshotField.scale}`);
6824
6929
  if (differences.length > 0) return {
6825
6930
  typeName,
6826
6931
  kind: "field_mismatch",
@@ -6840,6 +6945,7 @@ const SYSTEM_FIELDS = new Set(["id"]);
6840
6945
  * @returns {SchemaDrift[]} List of drifts detected
6841
6946
  */
6842
6947
  function compareRemoteWithSnapshot(remoteTypes, snapshot) {
6948
+ for (const type of Object.values(snapshot.types)) normalizeSnapshotType(type);
6843
6949
  const drifts = [];
6844
6950
  const remoteTypeMap = /* @__PURE__ */ new Map();
6845
6951
  for (const remoteType of remoteTypes) remoteTypeMap.set(remoteType.name, remoteType);
@@ -6905,6 +7011,189 @@ function formatSchemaDrifts(drifts) {
6905
7011
  return lines.join("\n");
6906
7012
  }
6907
7013
 
7014
+ //#endregion
7015
+ //#region src/cli/commands/tailordb/migrate/snapshot-manifest.ts
7016
+ /**
7017
+ * Convert a snapshot field config to proto format
7018
+ * @param {SnapshotFieldConfig} config - Snapshot field config
7019
+ * @returns {MessageInitShape<typeof TailorDBType_FieldConfigSchema>} Proto field config
7020
+ */
7021
+ function convertFieldConfigToProto(config) {
7022
+ const fieldEntry = {
7023
+ type: config.type,
7024
+ allowedValues: config.type === "enum" ? config.allowedValues?.map((v) => ({ ...v })) ?? [] : [],
7025
+ description: config.description || "",
7026
+ validate: toProtoSnapshotFieldValidate(config),
7027
+ array: config.array ?? false,
7028
+ index: config.index ?? false,
7029
+ unique: config.unique ?? false,
7030
+ foreignKey: config.foreignKey ?? false,
7031
+ foreignKeyType: config.foreignKeyType,
7032
+ foreignKeyField: config.foreignKeyField,
7033
+ required: config.required ?? true,
7034
+ vector: config.vector ?? false,
7035
+ ...toProtoSnapshotFieldHooks(config),
7036
+ ...config.serial && { serial: {
7037
+ start: BigInt(config.serial.start),
7038
+ ...config.serial.maxValue !== void 0 && { maxValue: BigInt(config.serial.maxValue) },
7039
+ ...config.serial.format && { format: config.serial.format }
7040
+ } },
7041
+ ...config.scale !== void 0 && { scale: config.scale }
7042
+ };
7043
+ if (config.type === "nested" && config.fields) fieldEntry.fields = processNestedFieldsFromSnapshot(config.fields);
7044
+ return fieldEntry;
7045
+ }
7046
+ function toProtoSnapshotFieldValidate(config) {
7047
+ return (config.validate ?? []).map((val) => ({
7048
+ action: TailorDBType_PermitAction.DENY,
7049
+ errorMessage: val.errorMessage || "",
7050
+ ...val.script && { script: { expr: val.script.expr ? `!${val.script.expr}` : "" } }
7051
+ }));
7052
+ }
7053
+ function toProtoSnapshotFieldHooks(config) {
7054
+ if (!config.hooks) return {};
7055
+ return { hooks: {
7056
+ create: config.hooks.create ? { expr: config.hooks.create.expr || "" } : void 0,
7057
+ update: config.hooks.update ? { expr: config.hooks.update.expr || "" } : void 0
7058
+ } };
7059
+ }
7060
+ /**
7061
+ * Process nested fields from snapshot format to proto format
7062
+ * @param {Record<string, SnapshotFieldConfig>} fields - Nested fields
7063
+ * @returns {Record<string, MessageInitShape<typeof TailorDBType_FieldConfigSchema>>} Proto nested fields
7064
+ */
7065
+ function processNestedFieldsFromSnapshot(fields) {
7066
+ const nestedFields = {};
7067
+ for (const [fieldName, fieldConfig] of Object.entries(fields)) if (fieldConfig.type === "nested" && fieldConfig.fields) {
7068
+ const deepNestedFields = processNestedFieldsFromSnapshot(fieldConfig.fields);
7069
+ nestedFields[fieldName] = {
7070
+ type: "nested",
7071
+ allowedValues: fieldConfig.allowedValues?.map((v) => ({ ...v })) ?? [],
7072
+ description: fieldConfig.description || "",
7073
+ validate: toProtoSnapshotFieldValidate(fieldConfig),
7074
+ required: fieldConfig.required ?? true,
7075
+ array: fieldConfig.array ?? false,
7076
+ index: false,
7077
+ unique: false,
7078
+ foreignKey: false,
7079
+ vector: false,
7080
+ ...toProtoSnapshotFieldHooks(fieldConfig),
7081
+ fields: deepNestedFields,
7082
+ ...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale }
7083
+ };
7084
+ } else nestedFields[fieldName] = {
7085
+ type: fieldConfig.type,
7086
+ allowedValues: fieldConfig.type === "enum" ? fieldConfig.allowedValues?.map((v) => ({ ...v })) ?? [] : [],
7087
+ description: fieldConfig.description || "",
7088
+ validate: toProtoSnapshotFieldValidate(fieldConfig),
7089
+ required: fieldConfig.required ?? true,
7090
+ array: fieldConfig.array ?? false,
7091
+ index: false,
7092
+ unique: false,
7093
+ foreignKey: false,
7094
+ vector: false,
7095
+ ...toProtoSnapshotFieldHooks(fieldConfig),
7096
+ ...fieldConfig.serial && { serial: {
7097
+ start: BigInt(fieldConfig.serial.start),
7098
+ ...fieldConfig.serial.maxValue !== void 0 && { maxValue: BigInt(fieldConfig.serial.maxValue) },
7099
+ ...fieldConfig.serial.format && { format: fieldConfig.serial.format }
7100
+ } },
7101
+ ...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale }
7102
+ };
7103
+ return nestedFields;
7104
+ }
7105
+
7106
+ //#endregion
7107
+ //#region src/cli/commands/tailordb/migrate/pre-migration-schema.ts
7108
+ /**
7109
+ * Pre-migration field config adjustments
7110
+ *
7111
+ * The Pre-phase sends a "relaxed" version of the target schema so that
7112
+ * `migrate.ts` scripts can still operate on the previous shape of the data.
7113
+ * This module handles the field-level adjustments:
7114
+ *
7115
+ * - `field_removed`: re-insert the removed field so migrate.ts can read it
7116
+ * (the physical drop happens in Post-phase).
7117
+ * - `field_added` with `required: true`: relax to `required: false`.
7118
+ * - `field_modified` optional→required, unique constraint added, enum
7119
+ * value removed: keep the looser side until Post-phase.
7120
+ *
7121
+ * Type-level deletions (`type_removed`) are handled by the deploy flow,
7122
+ * which retains the type until Post-phase rather than via this module.
7123
+ *
7124
+ * Post-phase then sends the final schema, after migrate.ts has had a chance
7125
+ * to fix up data.
7126
+ */
7127
+ /**
7128
+ * Diff change kinds that require pre-migration schema adjustments.
7129
+ */
7130
+ const PRE_MIGRATION_FIELD_KINDS = new Set([
7131
+ "field_added",
7132
+ "field_modified",
7133
+ "field_removed"
7134
+ ]);
7135
+ /**
7136
+ * Build a map of field changes that require pre-migration schema adjustment.
7137
+ * @param {PendingMigration[]} pendingMigrations - Pending migrations to scan
7138
+ * @returns {PreMigrationChangesMap} Map of changes keyed by typeName/fieldName
7139
+ */
7140
+ function buildPreMigrationChangesMap(pendingMigrations) {
7141
+ const map = /* @__PURE__ */ new Map();
7142
+ for (const migration of pendingMigrations) for (const change of migration.diff.changes) {
7143
+ if (!PRE_MIGRATION_FIELD_KINDS.has(change.kind)) continue;
7144
+ if (!change.fieldName) continue;
7145
+ const perType = map.get(change.typeName) ?? /* @__PURE__ */ new Map();
7146
+ perType.set(change.fieldName, change);
7147
+ map.set(change.typeName, perType);
7148
+ }
7149
+ return map;
7150
+ }
7151
+ /**
7152
+ * Apply pre-migration schema adjustments to a single field map in place.
7153
+ *
7154
+ * The fields map is the proto-shape `TailorDBType.schema.fields` that will
7155
+ * be sent in the Pre-phase. We mutate it so that:
7156
+ *
7157
+ * - Removed fields are re-inserted using their pre-migration config.
7158
+ * - Newly added required fields are relaxed to optional.
7159
+ * - Modified fields keep the looser side of unique/required/enum.
7160
+ *
7161
+ * @param {Record<string, MessageInitShape<typeof TailorDBType_FieldConfigSchema>>} fields - Field map to adjust (mutated in place)
7162
+ * @param {Map<string, DiffChange>} typeChanges - Changes for this type, keyed by fieldName
7163
+ */
7164
+ function applyPreMigrationFieldAdjustments(fields, typeChanges) {
7165
+ for (const [fieldName, change] of typeChanges) {
7166
+ if (change.kind === "field_removed") {
7167
+ const before = change.before;
7168
+ if (before) fields[fieldName] = convertFieldConfigToProto(before);
7169
+ continue;
7170
+ }
7171
+ const field = fields[fieldName];
7172
+ if (!field) continue;
7173
+ const before = change.before;
7174
+ const after = change.after;
7175
+ if (change.kind === "field_added" && after?.required) {
7176
+ field.required = false;
7177
+ continue;
7178
+ }
7179
+ if (change.kind !== "field_modified") continue;
7180
+ if (!before?.required && after?.required) field.required = false;
7181
+ if (!(before?.unique ?? false) && (after?.unique ?? false)) field.unique = false;
7182
+ if (before?.allowedValues && after?.allowedValues) {
7183
+ const afterValues = new Set(after.allowedValues.map((v) => v.value));
7184
+ if (before.allowedValues.filter((v) => !afterValues.has(v.value)).length > 0) {
7185
+ const valueMap = /* @__PURE__ */ new Map();
7186
+ for (const v of before.allowedValues) valueMap.set(v.value, v.description ?? "");
7187
+ for (const v of after.allowedValues) if (!valueMap.has(v.value)) valueMap.set(v.value, v.description ?? "");
7188
+ field.allowedValues = Array.from(valueMap.entries()).map(([value, description]) => ({
7189
+ value,
7190
+ description
7191
+ }));
7192
+ }
7193
+ }
7194
+ }
7195
+ }
7196
+
6908
7197
  //#endregion
6909
7198
  //#region src/cli/commands/tailordb/migrate/bundler.ts
6910
7199
  /**
@@ -7283,13 +7572,15 @@ async function detectPendingMigrations(client, workspaceId, namespacesWithMigrat
7283
7572
  if (!fs$1.existsSync(diffPath)) continue;
7284
7573
  const diff = loadDiff(diffPath);
7285
7574
  const scriptPath = getMigrationFilePath(migrationsDir, file.number, "migrate");
7286
- if (diff.requiresMigrationScript && !fs$1.existsSync(scriptPath)) {
7575
+ const hasScript = fs$1.existsSync(scriptPath);
7576
+ if (diff.requiresMigrationScript && !hasScript) {
7287
7577
  logger.warn(`Migration ${namespace}/${file.number} requires a script but migrate.ts not found`);
7288
7578
  continue;
7289
7579
  }
7290
7580
  pendingMigrations.push({
7291
7581
  number: file.number,
7292
7582
  scriptPath,
7583
+ hasScript,
7293
7584
  diffPath,
7294
7585
  namespace,
7295
7586
  migrationsDir,
@@ -7354,7 +7645,7 @@ async function updateMigrationLabel(client, workspaceId, namespace, migrationNum
7354
7645
  * @returns {Promise<void>}
7355
7646
  */
7356
7647
  async function executeMigrations(context, migrations) {
7357
- const migrationsWithScripts = migrations.filter((m) => m.diff.requiresMigrationScript);
7648
+ const migrationsWithScripts = migrations.filter((m) => m.hasScript);
7358
7649
  if (migrationsWithScripts.length === 0) return;
7359
7650
  const migrationsByNamespace = groupMigrationsByNamespace(migrationsWithScripts);
7360
7651
  for (const [namespace, namespaceMigrations] of migrationsByNamespace) {
@@ -7520,10 +7811,10 @@ function formatRemoteVerificationResults(results) {
7520
7811
  * Validate migration files and detect pending migrations
7521
7812
  * @param {OperatorClient} client - Operator client instance
7522
7813
  * @param {string} workspaceId - Workspace ID
7523
- * @param {ReadonlyMap<string, Record<string, TailorDBType>>} typesByNamespace - Types by namespace
7814
+ * @param {ReadonlyMap<string, Record<string, TailorDBSnapshotType>>} typesByNamespace - Types by namespace
7524
7815
  * @param {LoadedConfig} config - Loaded application config (includes path)
7525
7816
  * @param {boolean} noSchemaCheck - Whether to skip schema diff check
7526
- * @returns {Promise<PendingMigration[]>} List of pending migrations
7817
+ * @returns {Promise<ValidateAndDetectResult>} Pending migrations and namespaces that have migration directories configured
7527
7818
  */
7528
7819
  async function validateAndDetectMigrations(client, workspaceId, typesByNamespace, config, noSchemaCheck) {
7529
7820
  const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
@@ -7557,14 +7848,45 @@ async function validateAndDetectMigrations(client, workspaceId, typesByNamespace
7557
7848
  pendingMigrations = await detectPendingMigrations(client, workspaceId, namespacesWithMigrations);
7558
7849
  if (pendingMigrations.length > 0) {
7559
7850
  logger.newline();
7560
- const withScripts = pendingMigrations.filter((m) => m.diff.requiresMigrationScript);
7561
- const withoutScripts = pendingMigrations.filter((m) => !m.diff.requiresMigrationScript);
7851
+ const withScripts = pendingMigrations.filter((m) => m.hasScript);
7852
+ const withoutScripts = pendingMigrations.filter((m) => !m.hasScript);
7562
7853
  logger.info(`Applying ${pendingMigrations.length} migration(s):`);
7563
7854
  if (withoutScripts.length > 0) logger.info(` • ${withoutScripts.length} schema change(s) (applied automatically with schema deployment)`, { mode: "plain" });
7564
7855
  if (withScripts.length > 0) logger.info(` • ${withScripts.length} data migration(s) (requires migration script execution)`, { mode: "plain" });
7565
7856
  }
7566
7857
  }
7567
- return pendingMigrations;
7858
+ return {
7859
+ pendingMigrations,
7860
+ namespacesWithMigrations
7861
+ };
7862
+ }
7863
+ /**
7864
+ * Reconcile the on-remote migration label with the working tree's latest
7865
+ * migration number for each namespace.
7866
+ *
7867
+ * Used after a `--no-schema-check` apply: that flag skips the local/remote
7868
+ * snapshot drift checks, but if it also leaves the label untouched the remote
7869
+ * label can drift past the working tree's latest migration (e.g. when
7870
+ * checking out an older revision and re-deploying). A subsequent run would
7871
+ * then reconstruct the expected snapshot at a label that no longer exists in
7872
+ * the working tree, triggering a false drift error.
7873
+ *
7874
+ * Always force `label = working_tree_max` regardless of the previous label so
7875
+ * the invariant `label <= working_tree_max` is preserved.
7876
+ * @param client - Operator client instance
7877
+ * @param workspaceId - Workspace ID
7878
+ * @param namespacesWithMigrations - Namespaces that have migration directories configured
7879
+ */
7880
+ async function reconcileMigrationLabels(client, workspaceId, namespacesWithMigrations) {
7881
+ for (const { namespace, migrationsDir } of namespacesWithMigrations) {
7882
+ const targetVersion = getLatestMigrationNumber(migrationsDir);
7883
+ const currentVersion = await getRemoteMigrationNumber(client, workspaceId, namespace);
7884
+ await updateMigrationLabel(client, workspaceId, namespace, targetVersion);
7885
+ if (currentVersion !== targetVersion) {
7886
+ const from = currentVersion === null ? "<unset>" : formatMigrationNumber(currentVersion);
7887
+ logger.info(`Migration label for namespace ${namespace} reconciled: ${from} → ${formatMigrationNumber(targetVersion)}.`);
7888
+ }
7889
+ }
7568
7890
  }
7569
7891
  /**
7570
7892
  * Build migration execution context for script-based migrations.
@@ -7596,25 +7918,23 @@ async function applyTailorDB(client, result, phase = "create-update") {
7596
7918
  const { changeSet, context: migrationContext } = result;
7597
7919
  if (phase === "create-update") {
7598
7920
  const typesByNamespace = /* @__PURE__ */ new Map();
7599
- for (const tailordb of migrationContext.application.tailorDBServices) {
7600
- const types = tailordb.types;
7601
- if (types) typesByNamespace.set(tailordb.namespace, types);
7602
- }
7603
- const pendingMigrations = await validateAndDetectMigrations(client, migrationContext.workspaceId, typesByNamespace, migrationContext.config, migrationContext.noSchemaCheck);
7921
+ for (const tailordb of migrationContext.tailorDBInputs) typesByNamespace.set(tailordb.namespace, tailordb.types);
7922
+ const { pendingMigrations, namespacesWithMigrations } = await validateAndDetectMigrations(client, migrationContext.workspaceId, typesByNamespace, migrationContext.config, migrationContext.noSchemaCheck);
7604
7923
  if (pendingMigrations.length > 0) {
7605
7924
  processedTypes.reset();
7606
7925
  deletedResources.reset();
7926
+ migrationSnapshotCache.reset();
7607
7927
  await executeServicesCreation(client, changeSet);
7608
- const migrationsRequiringScripts = pendingMigrations.filter((m) => m.diff.requiresMigrationScript);
7928
+ const migrationsRequiringScripts = pendingMigrations.filter((m) => m.hasScript);
7609
7929
  const migrationCtx = migrationsRequiringScripts.length > 0 ? buildMigrationContextForScripts(client, migrationContext, migrationsRequiringScripts) : void 0;
7610
7930
  if (migrationsRequiringScripts.length > 0) {
7611
7931
  logger.info(`Executing ${migrationsRequiringScripts.length} data migration(s)...`);
7612
7932
  logger.newline();
7613
7933
  }
7614
7934
  for (const migration of pendingMigrations) {
7615
- await executeSingleMigrationPrePhase(client, changeSet, migration);
7616
- if (migration.diff.requiresMigrationScript && migrationCtx) await executeMigrations(migrationCtx, [migration]);
7617
- await executeSingleMigrationPostPhase(client, changeSet, migration);
7935
+ await executeSingleMigrationPrePhase(client, changeSet, migration, migrationContext.tailorDBInputs, migrationContext.executorUsedTypes);
7936
+ if (migration.hasScript && migrationCtx) await executeMigrations(migrationCtx, [migration]);
7937
+ await executeSingleMigrationPostPhase(client, changeSet, migration, migrationContext.tailorDBInputs, migrationContext.executorUsedTypes);
7618
7938
  await updateMigrationLabel(client, migrationContext.workspaceId, migration.namespace, migration.number);
7619
7939
  }
7620
7940
  if (migrationsRequiringScripts.length > 0) {
@@ -7640,6 +7960,7 @@ async function applyTailorDB(client, result, phase = "create-update") {
7640
7960
  await Promise.all(changeSet.gqlPermission.deletes.map((del) => client.deleteTailorDBGQLPermission(del.request)));
7641
7961
  await Promise.all(changeSet.type.deletes.map((del) => client.deleteTailorDBType(del.request)));
7642
7962
  }
7963
+ if (migrationContext.noSchemaCheck && namespacesWithMigrations.length > 0) await reconcileMigrationLabels(client, migrationContext.workspaceId, namespacesWithMigrations);
7643
7964
  } else if (phase === "delete-resources") {
7644
7965
  await Promise.all(changeSet.gqlPermission.deletes.map((del) => client.deleteTailorDBGQLPermission(del.request)));
7645
7966
  await Promise.all(changeSet.type.deletes.map((del) => client.deleteTailorDBType(del.request)));
@@ -7659,49 +7980,6 @@ function handleOptionalToRequiredError(error, messages) {
7659
7980
  throw error;
7660
7981
  }
7661
7982
  /**
7662
- * Build a map of breaking field changes from pending migrations
7663
- * @param {PendingMigration[]} pendingMigrations - Pending migrations
7664
- * @returns {BreakingChangesMap} Map of breaking changes
7665
- */
7666
- function buildBreakingChangesMap(pendingMigrations) {
7667
- const map = /* @__PURE__ */ new Map();
7668
- for (const migration of pendingMigrations) for (const change of migration.diff.changes) if (change.kind === "field_added" || change.kind === "field_modified" || change.kind === "field_removed") {
7669
- if (!change.fieldName) continue;
7670
- if (!map.has(change.typeName)) map.set(change.typeName, /* @__PURE__ */ new Map());
7671
- map.get(change.typeName).set(change.fieldName, change);
7672
- }
7673
- return map;
7674
- }
7675
- /**
7676
- * Apply pre-migration schema adjustments to avoid breaking changes before scripts run.
7677
- * @param fields - Field configs to adjust
7678
- * @param typeChanges - Breaking changes for a type
7679
- */
7680
- function applyPreMigrationFieldAdjustments(fields, typeChanges) {
7681
- for (const [fieldName, change] of typeChanges) {
7682
- const field = fields[fieldName];
7683
- if (!field) continue;
7684
- const before = change.before;
7685
- const after = change.after;
7686
- if (change.kind === "field_added" && after?.required) field.required = false;
7687
- if (change.kind !== "field_modified") continue;
7688
- if (!before?.required && after?.required) field.required = false;
7689
- if (!(before?.unique ?? false) && (after?.unique ?? false)) field.unique = false;
7690
- if (before?.allowedValues && after?.allowedValues) {
7691
- const afterValues = new Set(after.allowedValues.map((v) => v.value));
7692
- if (before.allowedValues.filter((v) => !afterValues.has(v.value)).length > 0) {
7693
- const valueMap = /* @__PURE__ */ new Map();
7694
- for (const v of before.allowedValues) valueMap.set(v.value, v.description ?? "");
7695
- for (const v of after.allowedValues) if (!valueMap.has(v.value)) valueMap.set(v.value, v.description ?? "");
7696
- field.allowedValues = Array.from(valueMap.entries()).map(([value, description]) => ({
7697
- value,
7698
- description
7699
- }));
7700
- }
7701
- }
7702
- }
7703
- }
7704
- /**
7705
7983
  * Get the set of type names affected by a migration
7706
7984
  * @param {PendingMigration} migration - Pending migration
7707
7985
  * @returns {Set<string>} Set of affected type names
@@ -7747,14 +8025,54 @@ const processedTypes = {
7747
8025
  }
7748
8026
  };
7749
8027
  /**
8028
+ * Snapshot cache for per-migration schema lookups during a single apply run.
8029
+ *
8030
+ * Only the initial baseline `0000/schema.json` is stored on disk; later migrations
8031
+ * ship `diff.json` only. To get the schema state AFTER migration N we replay the
8032
+ * initial snapshot through all diffs up to N via `reconstructSnapshotFromMigrations`.
8033
+ * Results are memoized per (namespace, migration number) for the apply run.
8034
+ */
8035
+ const migrationSnapshotCache = {
8036
+ cache: /* @__PURE__ */ new Map(),
8037
+ reset() {
8038
+ this.cache.clear();
8039
+ },
8040
+ load(migration) {
8041
+ const key = `${migration.namespace}/${migration.number}`;
8042
+ let snapshot = this.cache.get(key);
8043
+ if (!snapshot) {
8044
+ const reconstructed = reconstructSnapshotFromMigrations(migration.migrationsDir, migration.number);
8045
+ if (!reconstructed) throw new Error(`Cannot reconstruct snapshot for ${migration.namespace} migration ${migration.number}: no migrations found in ${migration.migrationsDir}`);
8046
+ snapshot = reconstructed;
8047
+ this.cache.set(key, snapshot);
8048
+ }
8049
+ return snapshot;
8050
+ }
8051
+ };
8052
+ /**
8053
+ * Build the TailorDBType manifest for `typeName` from migration N's snapshot.
8054
+ * @param migration - The pending migration whose snapshot to consult
8055
+ * @param typeName - The type name to look up in the snapshot
8056
+ * @param tailorDBInputs - Deploy inputs, used to resolve namespace gqlOperations
8057
+ * @param executorUsedTypes - Types used by executors (drives publishRecordEvents default)
8058
+ * @returns The manifest, or undefined if `typeName` is not in that snapshot.
8059
+ */
8060
+ function buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) {
8061
+ const snapshotType = migrationSnapshotCache.load(migration).types[typeName];
8062
+ if (!snapshotType) return void 0;
8063
+ return generateTailorDBTypeManifest(snapshotType, executorUsedTypes, tailorDBInputs.find((i) => i.namespace === migration.namespace)?.config.gqlOperations);
8064
+ }
8065
+ /**
7750
8066
  * Execute pre-migration phase for a single migration
7751
8067
  * @param {OperatorClient} client - Operator client instance
7752
8068
  * @param {TailorDBChangeSet} changeSet - TailorDB change set
7753
8069
  * @param {PendingMigration} migration - Single pending migration
8070
+ * @param tailorDBInputs - Deploy inputs, used to resolve namespace gqlOperations for the snapshot
8071
+ * @param executorUsedTypes - Types used by executors (drives publishRecordEvents default)
7754
8072
  * @returns {Promise<void>} Promise that resolves when pre-migration phase completes
7755
8073
  */
7756
- async function executeSingleMigrationPrePhase(client, changeSet, migration) {
7757
- const breakingChanges = buildBreakingChangesMap([migration]);
8074
+ async function executeSingleMigrationPrePhase(client, changeSet, migration, tailorDBInputs, executorUsedTypes) {
8075
+ const preMigrationChanges = buildPreMigrationChangesMap([migration]);
7758
8076
  const affectedTypes = getAffectedTypeNames(migration);
7759
8077
  const createdBeforeMigration = new Set(processedTypes.created);
7760
8078
  await Promise.all([
@@ -7763,11 +8081,13 @@ async function executeSingleMigrationPrePhase(client, changeSet, migration) {
7763
8081
  return typeName && affectedTypes.has(typeName) && !createdBeforeMigration.has(typeName);
7764
8082
  }).map((create) => {
7765
8083
  const typeName = create.request.tailordbType?.name;
8084
+ const snapshotType = typeName ? buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) : void 0;
8085
+ if (!snapshotType) return void 0;
7766
8086
  if (typeName) processedTypes.created.add(typeName);
7767
- const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
7768
- if (!typeChanges || typeChanges.size === 0) return client.createTailorDBType(create.request);
7769
8087
  const clonedRequest = structuredClone(create.request);
7770
- if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
8088
+ clonedRequest.tailordbType = snapshotType;
8089
+ const typeChanges = typeName ? preMigrationChanges.get(typeName) : void 0;
8090
+ if (typeChanges && typeChanges.size > 0 && clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
7771
8091
  return client.createTailorDBType(clonedRequest);
7772
8092
  }),
7773
8093
  ...changeSet.type.creates.filter((create) => {
@@ -7775,19 +8095,16 @@ async function executeSingleMigrationPrePhase(client, changeSet, migration) {
7775
8095
  return typeName && affectedTypes.has(typeName) && createdBeforeMigration.has(typeName);
7776
8096
  }).map((create) => {
7777
8097
  const typeName = create.request.tailordbType?.name;
8098
+ const snapshotType = typeName ? buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) : void 0;
8099
+ if (!snapshotType) return void 0;
7778
8100
  if (typeName) processedTypes.updated.add(typeName);
7779
- const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
7780
- if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType({
7781
- workspaceId: create.request.workspaceId,
7782
- namespaceName: create.request.namespaceName,
7783
- tailordbType: create.request.tailordbType
7784
- });
7785
- const clonedRequest = structuredClone(create.request);
7786
- if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
8101
+ const clonedTypeRequest = structuredClone(snapshotType);
8102
+ const typeChanges = typeName ? preMigrationChanges.get(typeName) : void 0;
8103
+ if (typeChanges && typeChanges.size > 0 && clonedTypeRequest.schema?.fields) applyPreMigrationFieldAdjustments(clonedTypeRequest.schema.fields, typeChanges);
7787
8104
  return client.updateTailorDBType({
7788
8105
  workspaceId: create.request.workspaceId,
7789
8106
  namespaceName: create.request.namespaceName,
7790
- tailordbType: clonedRequest.tailordbType
8107
+ tailordbType: clonedTypeRequest
7791
8108
  });
7792
8109
  }),
7793
8110
  ...changeSet.type.updates.filter((update) => {
@@ -7795,11 +8112,13 @@ async function executeSingleMigrationPrePhase(client, changeSet, migration) {
7795
8112
  return typeName && affectedTypes.has(typeName);
7796
8113
  }).map((update) => {
7797
8114
  const typeName = update.request.tailordbType?.name;
8115
+ const snapshotType = typeName ? buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) : void 0;
8116
+ if (!snapshotType) return void 0;
7798
8117
  if (typeName) processedTypes.updated.add(typeName);
7799
- const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
7800
- if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType(update.request);
7801
8118
  const clonedRequest = structuredClone(update.request);
7802
- if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
8119
+ clonedRequest.tailordbType = snapshotType;
8120
+ const typeChanges = typeName ? preMigrationChanges.get(typeName) : void 0;
8121
+ if (typeChanges && typeChanges.size > 0 && clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
7803
8122
  return client.updateTailorDBType(clonedRequest);
7804
8123
  })
7805
8124
  ]);
@@ -7836,24 +8155,40 @@ const deletedResources = {
7836
8155
  * @param {OperatorClient} client - Operator client instance
7837
8156
  * @param {TailorDBChangeSet} changeSet - TailorDB change set
7838
8157
  * @param {PendingMigration} migration - Single pending migration
8158
+ * @param tailorDBInputs - Deploy inputs, used to resolve namespace gqlOperations for the snapshot
8159
+ * @param executorUsedTypes - Types used by executors (drives publishRecordEvents default)
7839
8160
  * @returns {Promise<void>} Promise that resolves when post-migration phase completes
7840
8161
  */
7841
- async function executeSingleMigrationPostPhase(client, changeSet, migration) {
7842
- const breakingChanges = buildBreakingChangesMap([migration]);
8162
+ async function executeSingleMigrationPostPhase(client, changeSet, migration, tailorDBInputs, executorUsedTypes) {
8163
+ const preMigrationChanges = buildPreMigrationChangesMap([migration]);
7843
8164
  const affectedTypes = getAffectedTypeNames(migration);
7844
8165
  const deletedTypeNames = getDeletedTypeNames(migration);
7845
8166
  try {
7846
8167
  await Promise.all([...changeSet.type.creates.filter((create) => {
7847
8168
  const typeName = create.request.tailordbType?.name;
7848
- return typeName && affectedTypes.has(typeName) && breakingChanges.has(typeName);
7849
- }).map((create) => client.updateTailorDBType({
7850
- workspaceId: create.request.workspaceId,
7851
- namespaceName: create.request.namespaceName,
7852
- tailordbType: create.request.tailordbType
7853
- })), ...changeSet.type.updates.filter((update) => {
8169
+ return typeName && affectedTypes.has(typeName) && preMigrationChanges.has(typeName);
8170
+ }).map((create) => {
8171
+ const typeName = create.request.tailordbType?.name;
8172
+ const snapshotType = typeName ? buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) : void 0;
8173
+ if (!snapshotType) return void 0;
8174
+ return client.updateTailorDBType({
8175
+ workspaceId: create.request.workspaceId,
8176
+ namespaceName: create.request.namespaceName,
8177
+ tailordbType: snapshotType
8178
+ });
8179
+ }), ...changeSet.type.updates.filter((update) => {
7854
8180
  const typeName = update.request.tailordbType?.name;
7855
- return typeName && affectedTypes.has(typeName) && breakingChanges.has(typeName);
7856
- }).map((update) => client.updateTailorDBType(update.request))]);
8181
+ return typeName && affectedTypes.has(typeName) && preMigrationChanges.has(typeName);
8182
+ }).map((update) => {
8183
+ const typeName = update.request.tailordbType?.name;
8184
+ const snapshotType = typeName ? buildSnapshotTypeManifest(migration, typeName, tailorDBInputs, executorUsedTypes) : void 0;
8185
+ if (!snapshotType) return void 0;
8186
+ return client.updateTailorDBType({
8187
+ workspaceId: update.request.workspaceId,
8188
+ namespaceName: update.request.namespaceName,
8189
+ tailordbType: snapshotType
8190
+ });
8191
+ })]);
7857
8192
  } catch (error) {
7858
8193
  handleOptionalToRequiredError(error, ["This error occurred during post-migration phase. Please check your migration script.", "Ensure all existing records have values for fields being changed to required."]);
7859
8194
  }
@@ -7882,21 +8217,32 @@ async function executeSingleMigrationPostPhase(client, changeSet, migration) {
7882
8217
  }
7883
8218
  }
7884
8219
  /**
7885
- * Plan TailorDB-related changes based on current and desired state.
7886
- * @param context - Planning context
7887
- * @returns Planned changes
8220
+ * Convert a runtime TailorDBService to the snapshot-shaped deploy input.
8221
+ * @param service - Loaded TailorDB service (after `loadTypes()`)
8222
+ * @returns The canonical snapshot-shaped deploy input for downstream plan/apply phases.
7888
8223
  */
8224
+ function toTailorDBDeployInput(service) {
8225
+ const types = {};
8226
+ for (const [typeName, type] of Object.entries(service.types)) types[typeName] = createSnapshotType(type);
8227
+ return {
8228
+ namespace: service.namespace,
8229
+ config: service.config,
8230
+ types
8231
+ };
8232
+ }
7889
8233
  async function planTailorDB(context) {
7890
8234
  const { client, workspaceId, application, forRemoval, config, noSchemaCheck, forceApplyAll = false } = context;
7891
8235
  const tailordbs = [];
7892
8236
  if (!forRemoval) for (const tailordb of application.tailorDBServices) {
7893
8237
  await tailordb.loadTypes();
7894
- tailordbs.push(tailordb);
8238
+ tailordbs.push(toTailorDBDeployInput(tailordb));
7895
8239
  }
7896
8240
  const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
8241
+ const executorUsedTypes = /* @__PURE__ */ new Set();
8242
+ for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
7897
8243
  const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, application.id, tailordbs);
7898
8244
  const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
7899
- const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executors, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
8245
+ const [typeChangeSet, gqlPermissionChangeSet] = await Promise.all([planTypes(client, workspaceId, tailordbs, executorUsedTypes, deletedServices, void 0, forceApplyAll), planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll)]);
7900
8246
  return {
7901
8247
  changeSet: {
7902
8248
  service: serviceChangeSet,
@@ -7909,6 +8255,8 @@ async function planTailorDB(context) {
7909
8255
  context: {
7910
8256
  workspaceId,
7911
8257
  application,
8258
+ tailorDBInputs: tailordbs,
8259
+ executorUsedTypes,
7912
8260
  config,
7913
8261
  noSchemaCheck: noSchemaCheck ?? false
7914
8262
  }
@@ -8051,7 +8399,7 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
8051
8399
  resourceOwners
8052
8400
  };
8053
8401
  }
8054
- async function planTypes(client, workspaceId, tailordbs, executors, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
8402
+ async function planTypes(client, workspaceId, tailordbs, executorUsedTypes, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
8055
8403
  const changeSet = createChangeSet("TailorDB types");
8056
8404
  const fetchTypes = (namespaceName) => {
8057
8405
  return fetchAll(async (pageToken, maxPageSize) => {
@@ -8069,8 +8417,6 @@ async function planTypes(client, workspaceId, tailordbs, executors, deletedServi
8069
8417
  }
8070
8418
  });
8071
8419
  };
8072
- const executorUsedTypes = /* @__PURE__ */ new Set();
8073
- for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
8074
8420
  for (const tailordb of tailordbs) {
8075
8421
  const types = filteredTypesByNamespace?.get(tailordb.namespace) ?? tailordb.types;
8076
8422
  for (const typeName of Object.keys(types)) {
@@ -8201,8 +8547,8 @@ function isNumericLikeValue(value) {
8201
8547
  return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
8202
8548
  }
8203
8549
  /**
8204
- * Generate a TailorDB type manifest from parsed type
8205
- * @param {TailorDBType} type - Parsed TailorDB type
8550
+ * Generate a TailorDB type manifest from snapshot-shaped type
8551
+ * @param {TailorDBSnapshotType} type - Snapshot-shaped TailorDB type
8206
8552
  * @param {ReadonlySet<string>} executorUsedTypes - Set of types used by executors
8207
8553
  * @param {GqlOperations} [namespaceGqlOperations] - Default gqlOperations for the namespace (already normalized)
8208
8554
  * @returns {MessageInitShape<typeof TailorDBTypeSchema>} Type manifest
@@ -8229,7 +8575,7 @@ function generateTailorDBTypeManifest(type, executorUsedTypes, namespaceGqlOpera
8229
8575
  };
8230
8576
  const fields = {};
8231
8577
  Object.keys(type.fields).filter((fieldName) => fieldName !== "id").forEach((fieldName) => {
8232
- const fieldConfig = type.fields[fieldName].config;
8578
+ const fieldConfig = type.fields[fieldName];
8233
8579
  const fieldType = fieldConfig.type;
8234
8580
  const fieldEntry = {
8235
8581
  type: fieldType,
@@ -8242,7 +8588,7 @@ function generateTailorDBTypeManifest(type, executorUsedTypes, namespaceGqlOpera
8242
8588
  foreignKey: fieldConfig.foreignKey || false,
8243
8589
  foreignKeyType: fieldConfig.foreignKeyType,
8244
8590
  foreignKeyField: fieldConfig.foreignKeyField,
8245
- required: fieldConfig.required !== false,
8591
+ required: fieldConfig.required,
8246
8592
  vector: fieldConfig.vector || false,
8247
8593
  ...toProtoFieldHooks(fieldConfig),
8248
8594
  ...fieldConfig.serial && { serial: {
@@ -8256,14 +8602,14 @@ function generateTailorDBTypeManifest(type, executorUsedTypes, namespaceGqlOpera
8256
8602
  fields[fieldName] = fieldEntry;
8257
8603
  });
8258
8604
  const relationships = {};
8259
- for (const [relationName, rel] of Object.entries(type.forwardRelationships)) relationships[relationName] = {
8605
+ for (const [relationName, rel] of Object.entries(type.forwardRelationships ?? {})) relationships[relationName] = {
8260
8606
  refType: rel.targetType,
8261
8607
  refField: rel.sourceField,
8262
8608
  srcField: rel.targetField,
8263
8609
  array: rel.isArray,
8264
8610
  description: rel.description
8265
8611
  };
8266
- for (const [relationName, rel] of Object.entries(type.backwardRelationships)) relationships[relationName] = {
8612
+ for (const [relationName, rel] of Object.entries(type.backwardRelationships ?? {})) relationships[relationName] = {
8267
8613
  refType: rel.targetType,
8268
8614
  refField: rel.targetField,
8269
8615
  srcField: rel.sourceField,
@@ -8281,7 +8627,7 @@ function generateTailorDBTypeManifest(type, executorUsedTypes, namespaceGqlOpera
8281
8627
  if (type.files) Object.entries(type.files).forEach(([key, description]) => {
8282
8628
  files[key] = { description: description || "" };
8283
8629
  });
8284
- const permission = type.permissions.record ? protoPermission(type.permissions.record) : {
8630
+ const permission = type.permissions?.record ? protoPermission(type.permissions.record) : {
8285
8631
  create: [],
8286
8632
  read: [],
8287
8633
  update: [],
@@ -8327,7 +8673,7 @@ function processNestedFields(fields) {
8327
8673
  allowedValues: nestedFieldConfig.allowedValues || [],
8328
8674
  description: nestedFieldConfig.description || "",
8329
8675
  validate: toProtoFieldValidate(nestedFieldConfig),
8330
- required: nestedFieldConfig.required ?? true,
8676
+ required: nestedFieldConfig.required,
8331
8677
  array: nestedFieldConfig.array ?? false,
8332
8678
  index: false,
8333
8679
  unique: false,
@@ -8342,7 +8688,7 @@ function processNestedFields(fields) {
8342
8688
  allowedValues: nestedType === "enum" ? nestedFieldConfig.allowedValues || [] : [],
8343
8689
  description: nestedFieldConfig.description || "",
8344
8690
  validate: toProtoFieldValidate(nestedFieldConfig),
8345
- required: nestedFieldConfig.required ?? true,
8691
+ required: nestedFieldConfig.required,
8346
8692
  array: nestedFieldConfig.array ?? false,
8347
8693
  index: false,
8348
8694
  unique: false,
@@ -8360,9 +8706,12 @@ function processNestedFields(fields) {
8360
8706
  return nestedFields;
8361
8707
  }
8362
8708
  function protoPermission(permission) {
8363
- const ret = {};
8364
- for (const [key, policies] of Object.entries(permission)) ret[key] = policies.map((policy) => protoPolicy(policy));
8365
- return ret;
8709
+ return {
8710
+ create: permission.create.map((policy) => protoPolicy(policy)),
8711
+ read: permission.read.map((policy) => protoPolicy(policy)),
8712
+ update: permission.update.map((policy) => protoPolicy(policy)),
8713
+ delete: permission.delete.map((policy) => protoPolicy(policy))
8714
+ };
8366
8715
  }
8367
8716
  function protoPolicy(policy) {
8368
8717
  let permit;
@@ -8414,23 +8763,25 @@ function protoCondition(condition) {
8414
8763
  };
8415
8764
  }
8416
8765
  function protoOperand(operand) {
8417
- if (typeof operand === "object" && !Array.isArray(operand)) if ("user" in operand) return { kind: {
8418
- case: "userField",
8419
- value: operand.user
8420
- } };
8421
- else if ("record" in operand) return { kind: {
8422
- case: "recordField",
8423
- value: operand.record
8424
- } };
8425
- else if ("newRecord" in operand) return { kind: {
8426
- case: "newRecordField",
8427
- value: operand.newRecord
8428
- } };
8429
- else if ("oldRecord" in operand) return { kind: {
8430
- case: "oldRecordField",
8431
- value: operand.oldRecord
8432
- } };
8433
- else throw new Error(`Unknown operand: ${JSON.stringify(operand)}`);
8766
+ if (isSnapshotFieldRefOperand(operand)) {
8767
+ if ("user" in operand) return { kind: {
8768
+ case: "userField",
8769
+ value: operand.user
8770
+ } };
8771
+ if ("record" in operand) return { kind: {
8772
+ case: "recordField",
8773
+ value: operand.record
8774
+ } };
8775
+ if ("newRecord" in operand) return { kind: {
8776
+ case: "newRecordField",
8777
+ value: operand.newRecord
8778
+ } };
8779
+ if ("oldRecord" in operand) return { kind: {
8780
+ case: "oldRecordField",
8781
+ value: operand.oldRecord
8782
+ } };
8783
+ throw new Error(`Unknown field-ref operand shape: ${JSON.stringify(operand)}`);
8784
+ }
8434
8785
  return { kind: {
8435
8786
  case: "value",
8436
8787
  value: fromJson(ValueSchema, operand)
@@ -8462,7 +8813,7 @@ async function planGqlPermissions(client, workspaceId, tailordbs, deletedService
8462
8813
  });
8463
8814
  const types = tailordb.types;
8464
8815
  for (const typeName of Object.keys(types)) {
8465
- const gqlPermission = types[typeName].permissions.gql;
8816
+ const gqlPermission = types[typeName].permissions?.gql;
8466
8817
  if (!gqlPermission) continue;
8467
8818
  const desiredPermission = protoGqlPermission(gqlPermission);
8468
8819
  const existingPermission = existingGqlPermissions.find((entry) => entry.typeName === typeName);
@@ -8596,11 +8947,12 @@ function protoGqlCondition(condition) {
8596
8947
  };
8597
8948
  }
8598
8949
  function protoGqlOperand(operand) {
8599
- if (typeof operand === "object" && !Array.isArray(operand)) {
8950
+ if (isSnapshotFieldRefOperand(operand)) {
8600
8951
  if ("user" in operand) return { kind: {
8601
8952
  case: "userField",
8602
8953
  value: operand.user
8603
8954
  } };
8955
+ throw new Error(`Unsupported field-ref operand in GQL permission: ${JSON.stringify(operand)} — GQL permissions only support { user } field references`);
8604
8956
  }
8605
8957
  return { kind: {
8606
8958
  case: "value",
@@ -8609,7 +8961,7 @@ function protoGqlOperand(operand) {
8609
8961
  }
8610
8962
  /**
8611
8963
  * Check if there are schema differences between migration snapshots and local definitions
8612
- * @param {ReadonlyMap<string, Record<string, TailorDBType>>} typesByNamespace - Types by namespace
8964
+ * @param {ReadonlyMap<string, Record<string, TailorDBSnapshotType>>} typesByNamespace - Snapshot-shaped local types by namespace
8613
8965
  * @param {NamespaceWithMigrations[]} namespacesWithMigrations - Namespaces with migrations config
8614
8966
  * @returns {Promise<MigrationCheckResult[]>} Results for each namespace
8615
8967
  */
@@ -10957,6 +11309,7 @@ The \`--logs\` option displays logs from the downstream execution when available
10957
11309
  })
10958
11310
  }).strict(),
10959
11311
  run: async (args) => {
11312
+ await assertWritable({ profile: args.profile });
10960
11313
  const client = await initOperatorClient(await loadAccessToken({
10961
11314
  useProfile: true,
10962
11315
  profile: args.profile
@@ -12463,6 +12816,7 @@ const createCommand$1 = defineAppCommand({
12463
12816
  })
12464
12817
  }).strict(),
12465
12818
  run: async (args) => {
12819
+ await assertWritable();
12466
12820
  const folder = await createFolder({
12467
12821
  organizationId: args["organization-id"],
12468
12822
  parentFolderId: args["parent-folder-id"],
@@ -12501,6 +12855,7 @@ const deleteCommand$1 = defineAppCommand({
12501
12855
  ...confirmationArgs
12502
12856
  }).strict(),
12503
12857
  run: async (args) => {
12858
+ await assertWritable();
12504
12859
  const client = await initOperatorClient(await loadAccessToken());
12505
12860
  let folderName;
12506
12861
  try {
@@ -12651,6 +13006,7 @@ const updateCommand$2 = defineAppCommand({
12651
13006
  })
12652
13007
  }).strict(),
12653
13008
  run: async (args) => {
13009
+ await assertWritable();
12654
13010
  const folder = await updateFolder({
12655
13011
  organizationId: args["organization-id"],
12656
13012
  folderId: args["folder-id"],
@@ -12876,6 +13232,7 @@ const updateCommand$1 = defineAppCommand({
12876
13232
  })
12877
13233
  }).strict(),
12878
13234
  run: async (args) => {
13235
+ await assertWritable();
12879
13236
  const organization = await updateOrganization({
12880
13237
  organizationId: args["organization-id"],
12881
13238
  name: args.name
@@ -12980,6 +13337,7 @@ const removeCommand$1 = defineAppCommand({
12980
13337
  ...confirmationArgs
12981
13338
  }).strict(),
12982
13339
  run: async (args) => {
13340
+ await assertWritable({ profile: args.profile });
12983
13341
  const { client, workspaceId, application, config } = await loadOptions$10({
12984
13342
  workspaceId: args["workspace-id"],
12985
13343
  profile: args.profile,
@@ -13253,7 +13611,7 @@ function generateEmptyDbTypes(namespace) {
13253
13611
  }
13254
13612
  /**
13255
13613
  * Generate table type definition from a snapshot type
13256
- * @param {SnapshotType} type - Snapshot type
13614
+ * @param {TailorDBSnapshotType} type - Snapshot type
13257
13615
  * @param {BreakingChangeFieldInfo} breakingChangeFields - Breaking change field info
13258
13616
  * @returns {{ typeDef: string; usedTimestamp: boolean; usedColumnType: boolean }} Generated type and utility type usage
13259
13617
  */
@@ -13481,8 +13839,11 @@ function generateMigrationScript(diff) {
13481
13839
  return `/**
13482
13840
  * Migration script for ${diff.namespace}
13483
13841
  *
13484
- * This script handles data migration for breaking schema changes.
13485
- * Edit this file to implement your data migration logic.
13842
+ * This script runs between the Pre-migration and Post-migration phases of
13843
+ * 'tailor-sdk deploy'. Use it to transform existing data so that the schema
13844
+ * change can complete safely (for breaking changes, this is hard-required;
13845
+ * for warning-tier changes it is optional). Edit this file to implement
13846
+ * your data migration logic.
13486
13847
  *
13487
13848
  * The transaction is managed by the deploy command.
13488
13849
  * If any operation fails, all changes will be rolled back.
@@ -13643,7 +14004,7 @@ async function generate(options) {
13643
14004
  if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
13644
14005
  let pluginManager;
13645
14006
  if (plugins.length > 0) pluginManager = new PluginManager(plugins);
13646
- const { defineApplication } = await import("./application-BNkNt47b.mjs");
14007
+ const { defineApplication } = await import("./application-v_E2W-Fz.mjs");
13647
14008
  const application = defineApplication({
13648
14009
  config,
13649
14010
  pluginManager
@@ -13732,6 +14093,10 @@ async function generateDiffFromSnapshot(previousSnapshot, currentSnapshot, migra
13732
14093
  logger.newline();
13733
14094
  }
13734
14095
  }
14096
+ if (diff.hasWarnings) {
14097
+ logger.newline();
14098
+ logger.warn(formatWarnings(diff.warnings));
14099
+ }
13735
14100
  const result = await generateDiffFiles(diff, migrationsDir, getNextMigrationNumber(migrationsDir), previousSnapshot, options.name);
13736
14101
  logger.success(`Generated migration ${styles.bold(result.migrationNumber.toString().padStart(4, "0"))}`);
13737
14102
  logger.info(` Diff file: ${result.diffFilePath}`);
@@ -13755,6 +14120,10 @@ async function generateDiffFromSnapshot(previousSnapshot, currentSnapshot, migra
13755
14120
  } catch {
13756
14121
  return;
13757
14122
  }
14123
+ } else if (diff.hasWarnings) {
14124
+ logger.newline();
14125
+ logger.log(`Data loss is possible for this migration but no script was generated. To add a custom migrate.ts, run:`);
14126
+ logger.log(` ${styles.bold(`tailor-sdk tailordb migration script ${result.migrationNumber.toString().padStart(4, "0")} --namespace ${diff.namespace}`)}`);
13758
14127
  }
13759
14128
  }
13760
14129
  /**
@@ -13973,6 +14342,7 @@ const truncateCommand = defineAppCommand({
13973
14342
  })
13974
14343
  }).strict(),
13975
14344
  run: async (args) => {
14345
+ await assertWritable({ profile: args.profile });
13976
14346
  const types = args.types && args.types.length > 0 ? args.types : void 0;
13977
14347
  await $truncate({
13978
14348
  workspaceId: args["workspace-id"],
@@ -14355,9 +14725,11 @@ const createCommand = defineAppCommand({
14355
14725
  alias: "p",
14356
14726
  description: "Profile name to create"
14357
14727
  }),
14358
- "profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" })
14728
+ "profile-user": arg(z.string().optional(), { description: "User email for the profile (defaults to current user)" }),
14729
+ permission: arg(z.enum(["write", "read"]).default("write"), { description: "Profile permission (requires --profile-name). 'read' blocks all write commands while the profile is active." })
14359
14730
  }).strict(),
14360
14731
  run: async (args) => {
14732
+ await assertWritable();
14361
14733
  const workspace = await createWorkspace({
14362
14734
  name: args.name,
14363
14735
  region: args.region,
@@ -14375,13 +14747,15 @@ const createCommand = defineAppCommand({
14375
14747
  if (!config.users[profileUser]) throw new Error(`User "${profileUser}" not found.\nPlease verify your user name and login using 'tailor-sdk login' command.`);
14376
14748
  config.profiles[profileName] = {
14377
14749
  user: profileUser,
14378
- workspace_id: workspace.id
14750
+ workspace_id: workspace.id,
14751
+ ...args.permission === "read" ? { readonly: true } : {}
14379
14752
  };
14380
14753
  writePlatformConfig(config);
14381
14754
  profileInfo = {
14382
14755
  name: profileName,
14383
14756
  user: profileUser,
14384
- workspaceId: workspace.id
14757
+ workspaceId: workspace.id,
14758
+ permission: args.permission
14385
14759
  };
14386
14760
  if (!args.json) logger.success(`Profile "${profileName}" created successfully.`);
14387
14761
  }
@@ -14432,6 +14806,7 @@ const deleteCommand = defineAppCommand({
14432
14806
  ...confirmationArgs
14433
14807
  }).strict(),
14434
14808
  run: async (args) => {
14809
+ await assertWritable();
14435
14810
  const { client, workspaceId } = await loadOptions$7({ workspaceId: args["workspace-id"] });
14436
14811
  let workspace;
14437
14812
  try {
@@ -14569,6 +14944,7 @@ const restoreCommand = defineAppCommand({
14569
14944
  ...confirmationArgs
14570
14945
  }).strict(),
14571
14946
  run: async (args) => {
14947
+ await assertWritable();
14572
14948
  const { client, workspaceId } = await loadOptions$5({ workspaceId: args["workspace-id"] });
14573
14949
  if (!args.yes) {
14574
14950
  if (await prompt.text({ message: `Are you sure you want to restore workspace "${workspaceId}"? (yes/no):` }) !== "yes") {
@@ -14661,6 +15037,7 @@ const inviteCommand = defineAppCommand({
14661
15037
  })
14662
15038
  }).strict(),
14663
15039
  run: async (args) => {
15040
+ await assertWritable({ profile: args.profile });
14664
15041
  await inviteUser({
14665
15042
  workspaceId: args["workspace-id"],
14666
15043
  profile: args.profile,
@@ -14774,6 +15151,7 @@ const removeCommand = defineAppCommand({
14774
15151
  ...confirmationArgs
14775
15152
  }).strict(),
14776
15153
  run: async (args) => {
15154
+ await assertWritable({ profile: args.profile });
14777
15155
  if (!args.yes) {
14778
15156
  if (await prompt.text({ message: `Are you sure you want to remove user "${args.email}" from the workspace? (yes/no):` }) !== "yes") {
14779
15157
  logger.info("User removal cancelled.");
@@ -14838,6 +15216,7 @@ const updateCommand = defineAppCommand({
14838
15216
  })
14839
15217
  }).strict(),
14840
15218
  run: async (args) => {
15219
+ await assertWritable({ profile: args.profile });
14841
15220
  await updateUser({
14842
15221
  workspaceId: args["workspace-id"],
14843
15222
  profile: args.profile,
@@ -15791,5 +16170,5 @@ function isDeno() {
15791
16170
  }
15792
16171
 
15793
16172
  //#endregion
15794
- export { deleteCommand$1 as $, getMigrationDirPath as $t, truncate as A, executionsCommand as At, updateOrganization as B, MIGRATION_LABEL_KEY as Bt, listCommand$2 as C, toPageDirection as Cn, jobsCommand as Ct, resumeWorkflow as D, startWorkflow as Dt, resumeCommand as E, startCommand as Et, showCommand as F, getCommand$6 as Ft, getCommand$1 as G, INITIAL_SCHEMA_NUMBER as Gt, treeCommand as H, bundleMigrationScript as Ht, logBetaWarning as I, getExecutor as It, updateFolder as J, compareLocalTypesWithSnapshot as Jt, getOrganization as K, MIGRATE_FILE_NAME as Kt, remove as L, deploy as Lt, generate as M, listWorkflowExecutions as Mt, generateCommand as N, functionExecutionStatusToString as Nt, listCommand$3 as O, getCommand$5 as Ot, show as P, formatKeyValueTable as Pt, getFolder as Q, getLatestMigrationNumber as Qt, removeCommand$1 as R, executeScript as Rt, listApps as S, paginationArgs as Sn, getExecutorJob as St, healthCommand as T, watchExecutorJob as Tt, listCommand$4 as U, DB_TYPES_FILE_NAME as Ut, organizationTree as V, parseMigrationLabelNumber as Vt, listOrganizations as W, DIFF_FILE_NAME as Wt, listFolders as X, createSnapshotFromLocalTypes as Xt, listCommand$5 as Y, compareSnapshots as Yt, getCommand$2 as Z, formatMigrationNumber as Zt, getWorkspace as _, commonArgs as _n, webhookCommand as _t, updateUser as a, reconstructSnapshotFromMigrations as an, getCommand$3 as at, createCommand as b, isVerbose as bn, listCommand$9 as bt, listCommand as c, hasChanges as cn, tokenCommand as ct, inviteUser as d, trnPrefix as dn, generate$1 as dt, getMigrationFilePath as en, deleteFolder as et, restoreCommand as f, generateUserTypes as fn, listCommand$8 as ft, getCommand as g, defineAppCommand as gn, listWebhookExecutors as gt, listWorkspaces as h, apiCall as hn, getFunctionRegistry as ht, updateCommand as i, loadDiff as in, listOAuth2Clients as it, truncateCommand as j, getWorkflowExecution as jt, listWorkflows as k, getWorkflow as kt, listUsers as l, getNamespacesWithMigrations as ln, listCommand$7 as lt, listCommand$1 as m, apiCommand as mn, getCommand$4 as mt, query as n, getNextMigrationNumber as nn, createFolder as nt, removeCommand as o, formatDiffSummary as on, getOAuth2Client as ot, restoreWorkspace as p, prompt as pn, listFunctionRegistries as pt, updateCommand$2 as q, SCHEMA_FILE_NAME as qt, queryCommand as r, isValidMigrationNumber as rn, listCommand$6 as rt, removeUser as s, formatMigrationDiff as sn, getMachineUserToken as st, isNativeTypeScriptRuntime as t, getMigrationFiles as tn, createCommand$1 as tt, inviteCommand as u, sdkNameLabelKey as un, listMachineUsers as ut, deleteCommand as v, confirmationArgs as vn, triggerCommand as vt, getAppHealth as w, workspaceArgs as wn, listExecutorJobs as wt, createWorkspace as x, pagedLogArgs as xn, listExecutors as xt, deleteWorkspace as y, deploymentArgs as yn, triggerExecutor as yt, updateCommand$1 as z, waitForExecution$1 as zt };
15795
- //# sourceMappingURL=runtime-CNg0w27y.mjs.map
16173
+ export { listCommand$5 as $, compareSnapshots as $t, truncate as A, workspaceArgs as An, startCommand as At, logBetaWarning as B, getExecutor as Bt, listCommand$2 as C, configArg as Cn, triggerExecutor as Ct, resumeWorkflow as D, pagedLogArgs as Dn, jobsCommand as Dt, resumeCommand as E, isVerbose as En, getExecutorJob as Et, writeDbTypesFile as F, getWorkflowExecution as Ft, organizationTree as G, parseMigrationLabelNumber as Gt, removeCommand$1 as H, executeScript as Ht, getConfiguredEditorCommand as I, listWorkflowExecutions as It, listOrganizations as J, DIFF_FILE_NAME as Jt, treeCommand as K, bundleMigrationScript as Kt, openInConfiguredEditor as L, functionExecutionStatusToString as Lt, generate as M, getCommand$5 as Mt, generateCommand as N, getWorkflow as Nt, listCommand$3 as O, paginationArgs as On, listExecutorJobs as Ot, generateMigrationScript as P, executionsCommand as Pt, updateFolder as Q, compareLocalTypesWithSnapshot as Qt, show as R, formatKeyValueTable as Rt, listApps as S, commonArgs as Sn, triggerCommand as St, healthCommand as T, deploymentArgs as Tn, listExecutors as Tt, updateCommand$1 as U, waitForExecution$1 as Ut, remove as V, deploy as Vt, updateOrganization as W, MIGRATION_LABEL_KEY as Wt, getOrganization as X, MIGRATE_FILE_NAME as Xt, getCommand$1 as Y, INITIAL_SCHEMA_NUMBER as Yt, updateCommand$2 as Z, SCHEMA_FILE_NAME as Zt, getWorkspace as _, prompt as _n, listFunctionRegistries as _t, updateUser as a, getMigrationFiles as an, createCommand$1 as at, createCommand as b, assertWritable as bn, listWebhookExecutors as bt, listCommand as c, loadDiff as cn, listOAuth2Clients as ct, inviteUser as d, formatMigrationDiff as dn, getMachineUserToken as dt, createSnapshotFromLocalTypes as en, listFolders as et, restoreCommand as f, hasChanges as fn, tokenCommand as ft, getCommand as g, generateUserTypes as gn, listCommand$8 as gt, listWorkspaces as h, trnPrefix as hn, generate$1 as ht, updateCommand as i, getMigrationFilePath as in, deleteFolder as it, truncateCommand as j, startWorkflow as jt, listWorkflows as k, toPageDirection as kn, watchExecutorJob as kt, listUsers as l, reconstructSnapshotFromMigrations as ln, getCommand$3 as lt, listCommand$1 as m, sdkNameLabelKey as mn, listMachineUsers as mt, query as n, getLatestMigrationNumber as nn, getFolder as nt, removeCommand as o, getNextMigrationNumber as on, createFolder as ot, restoreWorkspace as p, getNamespacesWithMigrations as pn, listCommand$7 as pt, listCommand$4 as q, DB_TYPES_FILE_NAME as qt, queryCommand as r, getMigrationDirPath as rn, deleteCommand$1 as rt, removeUser as s, isValidMigrationNumber as sn, listCommand$6 as st, isNativeTypeScriptRuntime as t, formatMigrationNumber as tn, getCommand$2 as tt, inviteCommand as u, formatDiffSummary as un, getOAuth2Client as ut, deleteCommand as v, apiCommand as vn, getCommand$4 as vt, getAppHealth as w, confirmationArgs as wn, listCommand$9 as wt, createWorkspace as x, defineAppCommand as xn, webhookCommand as xt, deleteWorkspace as y, apiCall as yn, getFunctionRegistry as yt, showCommand as z, getCommand$6 as zt };
16174
+ //# sourceMappingURL=runtime-B2K6JW7V.mjs.map