@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.
- package/CHANGELOG.md +44 -0
- package/README.md +1 -1
- package/dist/{application-DUENhx4Y.mjs → application-CZMzt9jL.mjs} +3 -2
- package/dist/application-CZMzt9jL.mjs.map +1 -0
- package/dist/application-v_E2W-Fz.mjs +4 -0
- package/dist/brand-D-d15jx3.mjs.map +1 -1
- package/dist/cli/index.mjs +137 -25
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +1409 -1373
- package/dist/cli/lib.mjs +4 -4
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/skills.mjs.map +1 -1
- package/dist/client-_kHh0Pip.mjs.map +1 -1
- package/dist/configure/index.mjs.map +1 -1
- package/dist/crashreport-CvmdFs4i.mjs.map +1 -1
- package/dist/enum-constants-C3KSpsYj.mjs.map +1 -1
- package/dist/errors-pMPXghkO.mjs.map +1 -1
- package/dist/field-DLSIuMTu.mjs.map +1 -1
- package/dist/file-utils-DjNi_3U_.mjs.map +1 -1
- package/dist/interceptor-DTNS0EtF.mjs.map +1 -1
- package/dist/job-M3Avv_SV.mjs.map +1 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/kysely-type-B8aRz_oC.mjs.map +1 -1
- package/dist/logger-DTNAMYGy.mjs.map +1 -1
- package/dist/mock-BfL09ULZ.mjs.map +1 -1
- package/dist/multiline-e3IpANmS.mjs.map +1 -1
- package/dist/package-json-6Px8bDpG.mjs.map +1 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/repl-editor-jZ493eQI.mjs.map +1 -1
- package/dist/{runtime-CNg0w27y.mjs → runtime-B2K6JW7V.mjs} +543 -164
- package/dist/runtime-B2K6JW7V.mjs.map +1 -0
- package/dist/schema-C5QjYEc-.mjs.map +1 -1
- package/dist/secret-file-BHpxGyNf.mjs.map +1 -1
- package/dist/seed/index.mjs.map +1 -1
- package/dist/seed-DjfAn0BC.mjs.map +1 -1
- package/dist/service-DCgJxdg1.mjs.map +1 -1
- package/dist/telemetry-C1Y56L5E.mjs.map +1 -1
- package/dist/types-sir9UPht.mjs.map +1 -1
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/setup.mjs.map +1 -1
- package/docs/cli/tailordb.md +50 -0
- package/docs/cli/workspace.md +20 -17
- package/docs/cli-reference.md +1 -0
- package/docs/services/tailordb-migration.md +49 -31
- package/package.json +8 -8
- package/dist/application-BNkNt47b.mjs +0 -4
- package/dist/application-DUENhx4Y.mjs.map +0 -1
- package/dist/runtime-CNg0w27y.mjs.map +0 -1
- /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-
|
|
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
|
-
*
|
|
5827
|
-
*
|
|
5828
|
-
*
|
|
5829
|
-
*
|
|
5830
|
-
* @
|
|
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
|
|
5833
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
6433
|
-
|
|
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))
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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<
|
|
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.
|
|
7561
|
-
const withoutScripts = pendingMigrations.filter((m) => !m.
|
|
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
|
|
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.
|
|
7600
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
7780
|
-
|
|
7781
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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) &&
|
|
7849
|
-
}).map((create) =>
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
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) &&
|
|
7856
|
-
}).map((update) =>
|
|
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
|
-
*
|
|
7886
|
-
* @param
|
|
7887
|
-
* @returns
|
|
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,
|
|
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,
|
|
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
|
|
8205
|
-
* @param {
|
|
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]
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
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 (
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
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
|
|
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 (
|
|
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,
|
|
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 {
|
|
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
|
|
13485
|
-
*
|
|
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-
|
|
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 {
|
|
15795
|
-
//# sourceMappingURL=runtime-
|
|
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
|