Skip to content

Commit 7599e29

Browse files
toiroakrclaude
andcommitted
refactor: make rebuildBackwardRelationshipsForFilteredTypes a pure function
Remove side effects by returning a new object instead of mutating the input. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5889c93 commit 7599e29

4 files changed

Lines changed: 37 additions & 58 deletions

File tree

packages/sdk/src/cli/apply/index.ts

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -198,37 +198,14 @@ export async function apply(options?: ApplyOptions) {
198198
return;
199199
}
200200

201-
// Migration only mode: Apply IdP → Auth → TailorDB (different order for migration scripts)
202-
// Migration scripts require machine users, so IdP/Auth must be created first
203-
if (migrationOnlyMode) {
204-
// Phase 1: Create IdP/Auth first (machine users needed for migration scripts)
205-
await applyIdP(client, idp, "create-update");
206-
await applyAuth(client, auth, "create-update");
207-
208-
// Phase 2: TailorDB migration (machine users are now available)
209-
await applyTailorDB(client, tailorDB, "create-update");
210-
211-
// Phase 3: Delete services
212-
await applyAuth(client, auth, "delete-services");
213-
await applyIdP(client, idp, "delete-services");
214-
await applyTailorDB(client, tailorDB, "delete-services");
215-
216-
logger.success("Successfully applied TailorDB + Auth + IdP changes.");
217-
return;
218-
}
219-
220-
// Normal mode: Apply in standard order
221201
// Phase 2: Create/Update services that Application depends on
222202
// - Subgraph services (for GraphQL SDL composition): TailorDB, IdP, Auth, Pipeline
223203
// - StaticWebsite (for CORS and OAuth2 redirect URI resolution)
224-
225-
// TailorDB: Automatically validates migrations and handles migration flow internally
226-
await applyTailorDB(client, tailorDB, "create-update");
227-
228-
// Other services: Apply after TailorDB migrations complete
229-
await applyStaticWebsite(client, staticWebsite, "create-update");
204+
// IdP/Auth are applied before TailorDB because migration scripts require machine users
230205
await applyIdP(client, idp, "create-update");
231206
await applyAuth(client, auth, "create-update");
207+
await applyTailorDB(client, tailorDB, "create-update");
208+
await applyStaticWebsite(client, staticWebsite, "create-update");
232209
await applyPipeline(client, pipeline, "create-update");
233210

234211
// Phase 3: Delete subgraph resources (types, resolvers, etc.) before Application update

packages/sdk/src/cli/apply/services/tailordb/migration.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,11 @@ export async function buildFilteredTypesForVersion(
428428
}
429429
}
430430

431-
filteredTypesByNamespace.set(namespace, filteredTypes);
432-
}
433-
434-
// Rebuild backward relationships after all types are filtered
435-
// This ensures backward relationships only reference fields that exist in the filtered state
436-
for (const filteredTypes of filteredTypesByNamespace.values()) {
437-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
431+
// Rebuild backward relationships to only reference fields that exist in the filtered state
432+
filteredTypesByNamespace.set(
433+
namespace,
434+
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes),
435+
);
438436
}
439437

440438
return filteredTypesByNamespace;

packages/sdk/src/cli/tailordb/migrate/snapshot.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,13 +1924,13 @@ describe("snapshot", () => {
19241924
},
19251925
);
19261926

1927-
const filteredTypes = { Customer: customer, Order: order };
1928-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
1927+
const input = { Customer: customer, Order: order };
1928+
const result = rebuildBackwardRelationshipsForFilteredTypes(input);
19291929

19301930
// Customer should now have backward relationship to Order
1931-
expect(filteredTypes.Customer.backwardRelationships.orders).toBeDefined();
1932-
expect(filteredTypes.Customer.backwardRelationships.orders.targetType).toBe("Order");
1933-
expect(filteredTypes.Customer.backwardRelationships.orders.targetField).toBe("customerId");
1931+
expect(result.Customer.backwardRelationships.orders).toBeDefined();
1932+
expect(result.Customer.backwardRelationships.orders.targetType).toBe("Order");
1933+
expect(result.Customer.backwardRelationships.orders.targetField).toBe("customerId");
19341934
});
19351935

19361936
it("does not create backward relationships when forward relationship is filtered out", () => {
@@ -1943,11 +1943,11 @@ describe("snapshot", () => {
19431943
id: { name: "id", config: { type: "uuid", required: true } },
19441944
});
19451945

1946-
const filteredTypes = { Customer: customer, Order: order };
1947-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
1946+
const input = { Customer: customer, Order: order };
1947+
const result = rebuildBackwardRelationshipsForFilteredTypes(input);
19481948

19491949
// Customer should have no backward relationships
1950-
expect(Object.keys(filteredTypes.Customer.backwardRelationships)).toHaveLength(0);
1950+
expect(Object.keys(result.Customer.backwardRelationships)).toHaveLength(0);
19511951
});
19521952

19531953
it("does not create backward relationships when target type does not exist", () => {
@@ -1982,11 +1982,11 @@ describe("snapshot", () => {
19821982
);
19831983

19841984
// Only Order exists, Customer is not in filtered types
1985-
const filteredTypes = { Order: order };
1986-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
1985+
const input = { Order: order };
1986+
const result = rebuildBackwardRelationshipsForFilteredTypes(input);
19871987

19881988
// Order should still have its forward relationship but no backward should be created
1989-
expect(Object.keys(filteredTypes.Order.backwardRelationships)).toHaveLength(0);
1989+
expect(Object.keys(result.Order.backwardRelationships)).toHaveLength(0);
19901990
});
19911991

19921992
it("generates default backward name using inflection when not specified", () => {
@@ -2024,12 +2024,12 @@ describe("snapshot", () => {
20242024
},
20252025
);
20262026

2027-
const filteredTypes = { User: user, Profile: profile };
2028-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
2027+
const input = { User: user, Profile: profile };
2028+
const result = rebuildBackwardRelationshipsForFilteredTypes(input);
20292029

20302030
// Should generate "profiles" (plural of "profile")
2031-
expect(filteredTypes.User.backwardRelationships.profiles).toBeDefined();
2032-
expect(filteredTypes.User.backwardRelationships.profiles.isArray).toBe(true);
2031+
expect(result.User.backwardRelationships.profiles).toBeDefined();
2032+
expect(result.User.backwardRelationships.profiles.isArray).toBe(true);
20332033
});
20342034

20352035
it("uses singular form for unique (1-1) relationships", () => {
@@ -2067,12 +2067,12 @@ describe("snapshot", () => {
20672067
},
20682068
);
20692069

2070-
const filteredTypes = { User: user, Profile: profile };
2071-
rebuildBackwardRelationshipsForFilteredTypes(filteredTypes);
2070+
const input = { User: user, Profile: profile };
2071+
const result = rebuildBackwardRelationshipsForFilteredTypes(input);
20722072

20732073
// Should generate "profile" (singular) for 1-1 relationship
2074-
expect(filteredTypes.User.backwardRelationships.profile).toBeDefined();
2075-
expect(filteredTypes.User.backwardRelationships.profile.isArray).toBe(false);
2074+
expect(result.User.backwardRelationships.profile).toBeDefined();
2075+
expect(result.User.backwardRelationships.profile.isArray).toBe(false);
20762076
});
20772077
});
20782078
});

packages/sdk/src/cli/tailordb/migrate/snapshot.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,19 +1212,21 @@ export function filterTypeToSnapshot(type: TailorDBType, snapshotType: SnapshotT
12121212
* This must be called after all types have been filtered via filterTypeToSnapshot
12131213
* to ensure backward relationships only reference fields that exist in the filtered state.
12141214
* @param {Record<string, TailorDBType>} filteredTypes - Filtered types by name
1215+
* @returns {Record<string, TailorDBType>} New types with rebuilt backward relationships
12151216
*/
12161217
export function rebuildBackwardRelationshipsForFilteredTypes(
12171218
filteredTypes: Record<string, TailorDBType>,
1218-
): void {
1219-
// Clear all backward relationships first (they were set to {} in filterTypeToSnapshot)
1220-
for (const type of Object.values(filteredTypes)) {
1221-
type.backwardRelationships = {};
1219+
): Record<string, TailorDBType> {
1220+
// Create new types with empty backward relationships
1221+
const result: Record<string, TailorDBType> = {};
1222+
for (const [name, type] of Object.entries(filteredTypes)) {
1223+
result[name] = { ...type, backwardRelationships: {} };
12221224
}
12231225

12241226
// Rebuild backward relationships based on filtered forward relationships
1225-
for (const [typeName, type] of Object.entries(filteredTypes)) {
1227+
for (const [typeName, type] of Object.entries(result)) {
12261228
for (const rel of Object.values(type.forwardRelationships)) {
1227-
const targetType = filteredTypes[rel.targetType];
1229+
const targetType = result[rel.targetType];
12281230
if (!targetType) continue; // Target type doesn't exist in filtered types
12291231

12301232
const field = type.fields[rel.targetField];
@@ -1249,6 +1251,8 @@ export function rebuildBackwardRelationshipsForFilteredTypes(
12491251
};
12501252
}
12511253
}
1254+
1255+
return result;
12521256
}
12531257

12541258
// ============================================================================

0 commit comments

Comments
 (0)