Skip to content

feat: add SCHEMA_SYNC_SAFE flag to prevent destructive schema imports#96

Open
alphanull wants to merge 1 commit intobcc-code:mainfrom
alphanull:feat/schema-sync-safe
Open

feat: add SCHEMA_SYNC_SAFE flag to prevent destructive schema imports#96
alphanull wants to merge 1 commit intobcc-code:mainfrom
alphanull:feat/schema-sync-safe

Conversation

@alphanull
Copy link
Copy Markdown

When SCHEMA_SYNC_SAFE=true, all DELETE operations are filtered from the schema diff before applying. This allows project-specific collections, fields and relations to coexist with a base schema snapshot without being dropped on import.

Use case: multi-project setups where a base template schema is imported into projects that extend it with their own collections and fields.

When SCHEMA_SYNC_SAFE=true, all DELETE operations are filtered from the
schema diff before applying. This allows project-specific collections,
fields and relations to coexist with a base schema snapshot without
being dropped on import.

Use case: multi-project setups where a base template schema is imported
into projects that extend it with their own collections and fields.

Made-with: Cursor
@u12206050
Copy link
Copy Markdown
Member

What about using the SCHEMA_SYNC_MERGE or are there use cases for wanting to extend the schema but not the data?

@alphanull
Copy link
Copy Markdown
Author

Good point. They operate on different layers — SCHEMA_SYNC_MERGE currently only affects the data sync (ItemsService level), while SCHEMA_SYNC_SAFE filters destructive operations from the schema diff (SchemaService.diff()), which is a separate code path.

Combining both behaviors under SCHEMA_SYNC_MERGE would be a cleaner API. The concern is a breaking change for existing users who rely on SCHEMA_SYNC_MERGE=true for data while still expecting full schema replacement. If that's an acceptable trade-off (or documented as a behavior extension), we'd be happy to drop SCHEMA_SYNC_SAFE and extend SCHEMA_SYNC_MERGE to cover schema protection as well.

Our use case: we use schema-sync in a multi-project template setup. A base repository defines the schema, flows, dashboards and permissions which are are shared / used with all derived projects. Derived project repositories merge base updates via git merge upstream/main and redeploy. On startup, the project's Directus instance imports the updated base snapshot — but projects also extend the schema with their own collections and fields, which must not be dropped.

Without this flag, SchemaService.diff() generates DELETE operations for every project-specific collection and field not present in the base snapshot. We verified this experimentally: disabling the flag wiped all project-specific collections on the next import.

@u12206050 u12206050 requested a review from Copilot April 3, 2026 14:59
@u12206050
Copy link
Copy Markdown
Member

Ok, let's keep it separate. I'll review after copilot has given it a look

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in “safe sync” mode for schema imports so that importing a base snapshot won’t delete project-specific schema additions.

Changes:

  • Introduces SCHEMA_SYNC_SAFE env flag and passes it into schema sync options.
  • Adds filterNonDestructive() to remove DELETE operations from the schema diff before applying it.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/schemaExporter.ts Adds safe-mode diff filtering to prevent destructive schema apply operations.
src/index.ts Wires SCHEMA_SYNC_SAFE env var into SchemaExporter options.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 44 to 48
constructor(
protected getSchemaService: () => Promise<InstanceType<ExtensionsServices['SchemaService']>>,
protected logger: ApiExtensionContext['logger'],
protected options = { split: true }
protected options = { split: true, safe: false }
) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SchemaExporter now expects an options object that includes safe, but several call sites (e.g. the CLI export-schema action) pass { split } only. With strict TS settings, this makes the constructor parameter type too narrow and will cause a compile error. Consider typing options as a partial (e.g. { split?: boolean; safe?: boolean }) and merging with defaults inside the constructor so callers can omit safe.

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 18
const schemaOptions = {
split: typeof env.SCHEMA_SYNC_SPLIT === 'boolean' ? env.SCHEMA_SYNC_SPLIT : true,
safe: !!env.SCHEMA_SYNC_SAFE,
};
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schemaOptions now includes safe, but the CLI export-schema command later passes the raw args object (typed as { split: boolean }) into new SchemaExporter(...). After this change, that args object is missing the safe property and will not satisfy the constructor’s inferred option type under strict TS. Update the CLI path to merge defaults (e.g. pass { split: args.split, safe: schemaOptions.safe }) or change the constructor to accept partial options.

Copilot uses AI. Check for mistakes.
@u12206050
Copy link
Copy Markdown
Member

Could you apply the changes by copilot?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants