-
Notifications
You must be signed in to change notification settings - Fork 973
feat(core): content references database schema #1367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MA2153
wants to merge
8
commits into
emdash-cms:main
Choose a base branch
from
MA2153:content-reference-schema
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3e6c824
feat(core): add content references schema (relations + edges)
MA2153 335d361
test(core): cover content-reference unique constraints
MA2153 e39615e
test(core): cover content-reference traversal and self-refs
MA2153 4e41f5b
test(core): assert content-reference indexes and rollback
MA2153 ad0ad4d
fix(core): make migration 043 idempotent for partial-run recovery
MA2153 6dfc81b
chore: changeset for content references schema
MA2153 1e17da0
fix(core): make migration 043 down() idempotent too
MA2153 dbbf5dc
fix(core): enforce relation translation_group invariant; align index …
MA2153 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "emdash": minor | ||
| --- | ||
|
|
||
| Add content-reference database schema: `_emdash_relations` (relationship-type definitions, row-per-locale) and `_emdash_content_references` (directed, locale-agnostic edges between content entries linked by `translation_group`). Additive, forward-only migration `043`; no existing tables change. Groundwork for reference fields — no field type, API, or admin UI yet. | ||
121 changes: 121 additions & 0 deletions
121
packages/core/src/database/migrations/043_content_references.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import type { Kysely } from "kysely"; | ||
|
|
||
| import { getI18nConfig } from "../../i18n/config.js"; | ||
| import { currentTimestamp } from "../dialect-helpers.js"; | ||
|
|
||
| /** | ||
| * Content references. | ||
| * | ||
| * `_emdash_relations` defines relationship types (row-per-locale, mirroring | ||
| * `_emdash_taxonomy_defs`): which collection is the parent, which is the child | ||
| * (the side that may multiply), and localized labels for each role. | ||
| * | ||
| * `_emdash_content_references` holds directed `parent → child` edges between | ||
| * content entries. Both endpoints and the relation are referenced by | ||
| * `translation_group`, so edges are locale-agnostic. As with | ||
| * `content_taxonomies`, group-linking precludes SQL foreign keys; referential | ||
| * cleanup is an application-layer concern. | ||
| * | ||
| * Idempotency: every `CREATE TABLE` and `CREATE INDEX` uses `.ifNotExists()`, | ||
| * so a partial prior run (a crash mid-migration, or a retry after the runner's | ||
| * race-recovery path) re-applies cleanly — including any indexes that landed in | ||
| * the failed pass after their table. | ||
| */ | ||
| export async function up(db: Kysely<unknown>): Promise<void> { | ||
| const defaultLocale = getI18nConfig()?.defaultLocale ?? "en"; | ||
|
|
||
| await db.schema | ||
| .createTable("_emdash_relations") | ||
| .ifNotExists() | ||
| .addColumn("id", "text", (c) => c.primaryKey()) | ||
| .addColumn("name", "text", (c) => c.notNull()) | ||
| .addColumn("parent_collection", "text", (c) => c.notNull()) | ||
| .addColumn("child_collection", "text", (c) => c.notNull()) | ||
| .addColumn("parent_label", "text", (c) => c.notNull()) | ||
| .addColumn("child_label", "text", (c) => c.notNull()) | ||
| .addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale)) | ||
| .addColumn("translation_group", "text", (c) => c.notNull()) | ||
| .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))) | ||
| .addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db))) | ||
| .addUniqueConstraint("_emdash_relations_name_locale_unique", ["name", "locale"]) | ||
| .execute(); | ||
|
|
||
| await db.schema | ||
| .createIndex("idx__emdash_relations_locale") | ||
| .ifNotExists() | ||
| .on("_emdash_relations") | ||
|
MA2153 marked this conversation as resolved.
|
||
| .column("locale") | ||
| .execute(); | ||
| await db.schema | ||
| .createIndex("idx__emdash_relations_translation_group") | ||
| .ifNotExists() | ||
| .on("_emdash_relations") | ||
| .column("translation_group") | ||
| .execute(); | ||
| await db.schema | ||
| .createIndex("idx__emdash_relations_parent_collection") | ||
| .ifNotExists() | ||
| .on("_emdash_relations") | ||
| .column("parent_collection") | ||
| .execute(); | ||
| await db.schema | ||
| .createIndex("idx__emdash_relations_child_collection") | ||
| .ifNotExists() | ||
| .on("_emdash_relations") | ||
| .column("child_collection") | ||
| .execute(); | ||
|
|
||
|
MA2153 marked this conversation as resolved.
|
||
| // One row per (translation_group, locale): the row-per-locale model wants a | ||
| // relation to have at most one variant per locale. Migration 040 enforces | ||
| // the same invariant for `_emdash_bylines` with a *partial* unique | ||
| // (`WHERE translation_group IS NOT NULL`) only because it back-fills an | ||
| // existing table; this table is new and `translation_group` is `NOT NULL`, | ||
| // so a plain unique index suffices. | ||
| await db.schema | ||
| .createIndex("idx__emdash_relations_group_locale_unique") | ||
| .ifNotExists() | ||
| .unique() | ||
| .on("_emdash_relations") | ||
| .columns(["translation_group", "locale"]) | ||
| .execute(); | ||
|
|
||
| await db.schema | ||
| .createTable("_emdash_content_references") | ||
| .ifNotExists() | ||
| .addColumn("id", "text", (c) => c.primaryKey()) | ||
| .addColumn("relation_group", "text", (c) => c.notNull()) | ||
| .addColumn("parent_group", "text", (c) => c.notNull()) | ||
| .addColumn("child_group", "text", (c) => c.notNull()) | ||
| .addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0)) | ||
| .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db))) | ||
| .addUniqueConstraint("content_references_unique", [ | ||
| "relation_group", | ||
| "parent_group", | ||
| "child_group", | ||
| ]) | ||
| .execute(); | ||
|
|
||
| await db.schema | ||
| .createIndex("idx__emdash_content_references_parent") | ||
| .ifNotExists() | ||
| .on("_emdash_content_references") | ||
| .columns(["parent_group", "relation_group", "sort_order"]) | ||
| .execute(); | ||
| await db.schema | ||
| .createIndex("idx__emdash_content_references_child") | ||
| .ifNotExists() | ||
| .on("_emdash_content_references") | ||
| .columns(["child_group", "relation_group"]) | ||
| .execute(); | ||
| await db.schema | ||
| .createIndex("idx__emdash_content_references_relation") | ||
| .ifNotExists() | ||
| .on("_emdash_content_references") | ||
| .column("relation_group") | ||
| .execute(); | ||
| } | ||
|
|
||
| export async function down(db: Kysely<unknown>): Promise<void> { | ||
| await db.schema.dropTable("_emdash_content_references").ifExists().execute(); | ||
| await db.schema.dropTable("_emdash_relations").ifExists().execute(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion] This is net-new feature groundwork. While the package is pre-1.0, a
minorbump is more idiomatic in Changesets for additive capabilities thanpatch.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bumped to
minor.