@murumets-ee/content 0.34.1 → 0.35.1
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/dist/client.d.mts +2 -2
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/entities/index.d.mts +1 -1
- package/dist/{index-kQaEm4Ft.d.mts → index-D8nN7BFd.d.mts} +22 -3
- package/dist/index-D8nN7BFd.d.mts.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/locale-utils-CuIBKeaz.mjs +2 -0
- package/dist/locale-utils-CuIBKeaz.mjs.map +1 -0
- package/dist/lock-service.d.mts +1 -2
- package/dist/lock-service.d.mts.map +1 -1
- package/dist/plugin-IW9SM9iO.mjs +2 -0
- package/dist/plugin-IW9SM9iO.mjs.map +1 -0
- package/dist/plugin.d.mts +18 -5
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -1
- package/dist/query-client.d.mts +0 -1
- package/dist/query-client.d.mts.map +1 -1
- package/dist/query-client.mjs +1 -1
- package/dist/routing-strategy-DGx6Rq6F.mjs +2 -0
- package/dist/routing-strategy-DGx6Rq6F.mjs.map +1 -0
- package/dist/routing-strategy-TPSzrapD.d.mts +121 -0
- package/dist/routing-strategy-TPSzrapD.d.mts.map +1 -0
- package/dist/{types-yRLOOCOn.d.mts → types-CUw2Pf0Y.d.mts} +36 -2
- package/dist/types-CUw2Pf0Y.d.mts.map +1 -0
- package/package.json +7 -6
- package/dist/errors-bZAGPYhp.mjs +0 -2
- package/dist/errors-bZAGPYhp.mjs.map +0 -1
- package/dist/index-kQaEm4Ft.d.mts.map +0 -1
- package/dist/locale-utils-w0WO6J_P.mjs +0 -2
- package/dist/locale-utils-w0WO6J_P.mjs.map +0 -1
- package/dist/plugin-CG2tNeoG.mjs +0 -2
- package/dist/plugin-CG2tNeoG.mjs.map +0 -1
- package/dist/types-yRLOOCOn.d.mts.map +0 -1
package/dist/plugin-CG2tNeoG.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./errors-bZAGPYhp.mjs";import{n as t}from"./locks-5779ugSW.mjs";import{definePlugin as n}from"@murumets-ee/core";import{column as r,defineTable as i,schemaRegistry as a}from"@murumets-ee/db";import{eq as o,sql as s}from"drizzle-orm";const c=Symbol.for(`@murumets-ee/content:content-tables-registry`);function l(){let e=globalThis,t=e[c];return t||(t=new Map,e[c]=t),t}function u(e,t){let n=l(),r=n.get(e)??{};n.set(e,{...r,...t})}function d(e){return l().get(e)??{}}const f=Symbol.for(`@murumets-ee/content:locale-status-writer`);async function p(e,t,n,r){let i=r===`published`?new Date:null;await e.upsert({entityId:t,locale:n,status:r,publishedAt:i},{target:[`entityId`,`locale`],set:{status:r,publishedAt:i}})}function m(t){globalThis[f]=(n,r)=>{let{localeStatus:i}=d(n.name);if(i)return async(n,a,o)=>{if(!t.includes(a))throw new e(a,t);await p(i.makeClient(r),n,a,o)}}}function h(){return{async writeDraft({documentType:e,documentId:t,locale:n,dataPatch:r,user:i,tx:a}){let{drafts:o}=d(e);if(!o)return!1;let s=o.makeClient(a),c={...(await s.findOne({entityId:t,locale:n}))?.data??{},...r},l=new Date;return await s.upsert({entityId:t,locale:n,data:c,createdBy:i.id,createdByName:i.name??null,createdAt:l,updatedAt:l},{target:[`entityId`,`locale`],set:{data:c,updatedAt:l}}),!0},async hasAnyDraft({documentType:e,documentId:t,db:n}){let{drafts:r}=d(e);return r?r.makeClient(n).exists({entityId:t}):!1},async hasAnyLocaleActivity({documentType:e,documentId:t,db:n}){let{localeStatus:r}=d(e);if(r&&await r.makeClient(n).exists({entityId:t}))return!0;let i=a.get(`${e}_translations`);if(i){let e=i.entityId;if(e!==void 0&&(await n.select({one:s`1`}).from(i).where(o(e,t)).limit(1)).length>0)return!0}return!1}}}function g(e){return i({name:`${e.name}_drafts`,columns:{id:r.uuid({primaryKey:!0,defaultRandom:!0}),entityId:r.uuid({notNull:!0,pgName:`entity_id`}),locale:r.varchar({length:10,notNull:!0,default:`_`}),data:r.jsonb({notNull:!0}),createdBy:r.varchar({length:255,notNull:!0,pgName:`created_by`}),createdByName:r.varchar({length:255,pgName:`created_by_name`}),createdAt:r.timestamp({notNull:!0,defaultNow:!0,withTimezone:!0,pgName:`created_at`}),updatedAt:r.timestamp({notNull:!0,defaultNow:!0,withTimezone:!0,pgName:`updated_at`})},unique:[{on:[`entityId`,`locale`]}]})}function _(e){return i({name:`${e.name}_locale_status`,columns:{id:r.uuid({primaryKey:!0,defaultRandom:!0}),entityId:r.uuid({notNull:!0,pgName:`entity_id`}),locale:r.varchar({length:10,notNull:!0}),status:r.varchar({length:20,notNull:!0,default:`draft`}),publishedAt:r.timestamp({withTimezone:!0,pgName:`published_at`})},unique:[{on:[`entityId`,`locale`]}]})}function v(e){return i({name:`${e.name}_versions`,columns:{id:r.uuid({primaryKey:!0,defaultRandom:!0}),entityId:r.uuid({notNull:!0,pgName:`entity_id`}),version:r.integer({notNull:!0}),locale:r.varchar({length:10,notNull:!0,default:`_`}),data:r.jsonb({notNull:!0}),delta:r.jsonb(),status:r.varchar({length:20}),createdBy:r.varchar({length:255,pgName:`created_by`}),createdByName:r.varchar({length:255,pgName:`created_by_name`}),createdAt:r.timestamp({notNull:!0,defaultNow:!0,withTimezone:!0,pgName:`created_at`}),isAutosave:r.boolean({notNull:!0,default:!1,pgName:`is_autosave`})},unique:[{on:[`entityId`,`version`,`locale`]}]})}const y=Symbol.for(`@murumets-ee/content:content-config`);function b(){let e=globalThis[y];if(!e)throw Error(`@murumets-ee/content plugin not initialized. Add content() to your plugins array.`);return e}function x(e){if(!e.locales||e.locales.length===0)throw Error(`content() requires at least one locale`);if(!e.defaultLocale)throw Error(`content() requires a defaultLocale`);if(!e.locales.some(t=>t.code===e.defaultLocale))throw Error(`defaultLocale "${e.defaultLocale}" not found in locales: [${e.locales.map(e=>e.code).join(`, `)}]`);let r=e.autoTrash??!0,i=r===!1?void 0:{publishable:!0,...typeof r==`object`&&r.exclude?{exclude:r.exclude}:{}};return n({name:`@murumets-ee/content`,contributes:{"blocks.documentDraftStore":h(),...i?{"trash.autoEnable":i}:{}},server:{tables:{toolkitContentLocks:t},init:async t=>{globalThis[y]=e;let n=0,r=0,i=0;for(let[e,o]of t.entities){if(o.behaviors?.some(e=>e.name===`versionable`)){let t=`${e}_versions`;if(!a.has(t)){let n=v(o);a.register(t,n.table),u(e,{versions:n})}n++}let t=o.behaviors?.some(e=>e.name===`publishable`),s=o.allFields,c=Object.values(s).some(e=>e.translatable);if(t&&c){let t=`${e}_locale_status`;if(!a.has(t)){let n=_(o);a.register(t,n.table),u(e,{localeStatus:n})}r++}if(t){let t=`${e}_drafts`;if(!a.has(t)){let n=g(o);a.register(t,n.table),u(e,{drafts:n})}i++}}m(e.locales.map(e=>e.code)),t.logger.info({locales:e.locales.map(e=>e.code),defaultLocale:e.defaultLocale,versionableEntities:n,localeStatusEntities:r,draftEntities:i},`Content plugin initialized`)}}})}export{d as i,b as n,p as r,x as t};
|
|
2
|
-
//# sourceMappingURL=plugin-CG2tNeoG.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-CG2tNeoG.mjs","names":["cols"],"sources":["../src/schema/registry.ts","../src/locale-status-slot.ts","../src/document-draft-store.ts","../src/schema/drafts.ts","../src/schema/locale-status.ts","../src/schema/versions.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Per-entity content table registry.\n *\n * Maps an entity name to its content infrastructure tables — drafts,\n * versions, locale_status. Each entry stores the `DefinedTable` (from\n * `defineTable`), which exposes a typed `.makeClient(db)` for CRUD via\n * `TableClient` and a raw `.table` for legacy callers.\n *\n * The content plugin populates this registry at `init` time; ContentClient\n * reads from it lazily on first use. Tables that don't apply to an entity\n * (non-publishable entities have no drafts, etc.) are simply absent.\n */\n\nimport type { DraftsTable } from './drafts.js'\nimport type { LocaleStatusTable } from './locale-status.js'\nimport type { VersionsTable } from './versions.js'\n\n/**\n * The full set of optional content infra tables attached to an entity.\n * Every field is optional — entities without the relevant behavior don't\n * get the corresponding table.\n */\nexport interface ContentTables {\n /** `{entity}_drafts` — present when the entity has `publishable()`. */\n drafts?: DraftsTable\n /** `{entity}_versions` — present when the entity has `versionable()`. */\n versions?: VersionsTable\n /** `{entity}_locale_status` — present when the entity is publishable + has translatable fields. */\n localeStatus?: LocaleStatusTable\n}\n\n/**\n * The registry map lives on `globalThis` keyed by `Symbol.for(...)`, NOT as a\n * module-level binding — matching core's `PluginContributionsRegistry`\n * pattern. A module-level `Map` here is the exact Turbopack-dev footgun\n * CLAUDE.md documents (the createdBy/updatedBy bug class): a lazily-compiled\n * route chunk can resolve this module to a DIFFERENT instance than the boot\n * path that called `registerContentTables`, so `getContentTables()` returns\n * `{}` and callers silently take their no-table fallbacks. Observed in the\n * wild 2026-06-11 (F031 #1): the blocks op-commit route saw an empty registry,\n * `writeDraft` fell back to live-row writes, and Publish 500'd with \"No draft\n * found\" — flaky per-recompile, dev-only. `Symbol.for` is process-global, so\n * every duplicate module instance reads the same map.\n */\nconst REGISTRY_KEY = Symbol.for('@murumets-ee/content:content-tables-registry')\n\ninterface GlobalWithRegistry {\n [REGISTRY_KEY]?: Map<string, ContentTables>\n}\n\nfunction registryMap(): Map<string, ContentTables> {\n const g = globalThis as GlobalWithRegistry\n let map = g[REGISTRY_KEY]\n if (!map) {\n map = new Map<string, ContentTables>()\n g[REGISTRY_KEY] = map\n }\n return map\n}\n\n/**\n * Register content tables for an entity. Called from `content().init()`.\n *\n * Subsequent calls for the same entity name MERGE the provided patch into\n * the existing entry. This lets the plugin add tables one at a time (drafts,\n * then versions, then locale_status) without the caller needing to build the\n * whole object up front.\n */\nexport function registerContentTables(entityName: string, patch: ContentTables): void {\n const tables = registryMap()\n const existing = tables.get(entityName) ?? {}\n tables.set(entityName, { ...existing, ...patch })\n}\n\n/**\n * Get the content tables for an entity. Returns an empty object (rather\n * than undefined) when the entity has no registered tables — that\n * simplifies the common ContentClient lookup idiom:\n *\n * const { drafts } = getContentTables(entity.name)\n * if (!drafts) return null\n */\nexport function getContentTables(entityName: string): ContentTables {\n return registryMap().get(entityName) ?? {}\n}\n\n/**\n * Clear the registry. **Test-only.** Throws in production.\n */\nexport function clearContentTables(): void {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'clearContentTables() is not allowed in production — this is a test-only utility.',\n )\n }\n registryMap().clear()\n}\n","/**\n * Process-global slot exposing the content plugin's per-locale publish\n * status writer to `AdminClient.updateForLocale` (SD021 — the\n * locale-write choke point).\n *\n * `@murumets-ee/core`'s `createAdminClient` reads the slot by symbol\n * identity when constructing a client, so EVERY framework-built\n * AdminClient routes non-default-locale `status` writes into\n * `<entity>_locale_status` automatically — future writers (import\n * pipelines, AI integration, workflow actions) cannot forget the\n * routing. Same `globalThis` + `Symbol.for` shape as queue's\n * `enqueue-on-commit-slot.ts`: survives HMR and avoids a dependency\n * edge from core onto content.\n *\n * NOTE: this module must stay free of `import 'server-only'` — it is in\n * the content plugin's `init` load path, which the `lumi` CLI loads in\n * plain Node (see memory: server-only in the CLI load path breaks every\n * command and only scaffold-e2e catches it).\n */\n\nimport type { Entity } from '@murumets-ee/entity'\nimport type { LocaleStatusWriter } from '@murumets-ee/entity/admin'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { InvalidLocaleError } from './errors.js'\nimport type { LocaleStatusTable } from './schema/locale-status.js'\nimport { getContentTables } from './schema/registry.js'\n\nconst LOCALE_STATUS_WRITER_KEY = Symbol.for('@murumets-ee/content:locale-status-writer')\n\n/** The typed TableClient for a `{entity}_locale_status` table. */\ntype LocaleStatusClient = ReturnType<LocaleStatusTable['makeClient']>\n\n/**\n * Upsert one non-default locale's status row. The single implementation\n * shared by `setLocaleStatus` (content's own API) and the slot writer\n * below — keeping the `publishedAt` semantics in one place.\n *\n * `client` is the locale-status TableClient; callers resolve it from\n * `getContentTables(entity).localeStatus` (capability-gated).\n */\nexport async function upsertLocaleStatusRow(\n client: LocaleStatusClient,\n entityId: string,\n locale: string,\n status: 'draft' | 'published',\n): Promise<void> {\n const publishedAt = status === 'published' ? new Date() : null\n await client.upsert(\n { entityId, locale, status, publishedAt },\n { target: ['entityId', 'locale'], set: { status, publishedAt } },\n )\n}\n\n/**\n * Write the locale-status writer factory into the slot. Called from the\n * content plugin's `init` AFTER the per-entity `<entity>_locale_status`\n * tables are registered (the factory consults that registry per entity).\n *\n * `localeCodes` is the plugin's configured locale set, passed in by init\n * rather than read via `getContentConfig()` — importing `locale-utils`/\n * `plugin` here would close an import cycle (plugin → slot → … → plugin;\n * `verify:cycles` catches it).\n *\n * The factory returns `undefined` for entities without a locale-status\n * table — `updateForLocale` then falls back to the base `status` column,\n * which is correct: for non-translatable publishables the default locale\n * is the only locale.\n */\nexport function setActiveLocaleStatusWriterFactory(localeCodes: readonly string[]): void {\n const factory = (entity: Entity, db: PostgresJsDatabase): LocaleStatusWriter | undefined => {\n const { localeStatus } = getContentTables(entity.name)\n if (!localeStatus) return undefined\n return async (entityId, locale, status) => {\n // Same check as locale-utils' validateLocale (sans the '_' sentinel —\n // updateForLocale never sends it to the status writer): unvalidated\n // locales must not mint locale_status rows no reader will ever merge.\n if (!localeCodes.includes(locale)) {\n throw new InvalidLocaleError(locale, localeCodes)\n }\n // The caller (core's createAdminClient) passes the SAME db handle the\n // AdminClient writes through — a client built against an explicit app\n // (tests, multi-app setups) must not write its locale-status rows\n // through the singleton app's connection.\n const client = localeStatus.makeClient(db)\n await upsertLocaleStatusRow(client, entityId, locale, status)\n }\n }\n ;(globalThis as Record<symbol, unknown>)[LOCALE_STATUS_WRITER_KEY] = factory\n}\n","/**\n * `DocumentDraftStore` implementation contributed to the block editor's\n * `blocks.documentDraftStore` slot (SD015).\n *\n * The block editor's op-commit route (in `@murumets-ee/blocks`) materializes a\n * `withOpLog()` document's working tree by calling this store inside its\n * commit transaction — so block edits land in the per-locale `{entity}_drafts`\n * buffer, never the live/public row. A later \"Publish\" (`publishDraft`)\n * promotes the draft to the live row + snapshots a version.\n *\n * blocks owns the slot type (imported `type`-only below — that import also\n * pulls the `PluginContributionSlots` augmentation into this compilation);\n * content owns the draft tables. No runtime dependency between the packages.\n *\n * NB: no `import 'server-only'` here. This module is imported by\n * `content/plugin.ts` at construct time (for the `contributes` slot), and the\n * content plugin must stay loadable in a plain-Node context (the `lumi migrate`\n * CLI loads `lumi.config.ts` without the `react-server` resolution condition,\n * where `server-only` throws). The store is only ever *called* server-side (by\n * the op-commit committer) and is never reachable from a client bundle.\n */\n\nimport type { DocumentDraftStore } from '@murumets-ee/blocks/server'\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { eq, sql } from 'drizzle-orm'\nimport type { PgColumn } from 'drizzle-orm/pg-core'\nimport { getContentTables } from './schema/registry.js'\n\n/**\n * Build the draft store. Stateless — resolves the per-entity drafts table from\n * the schema registry at call time, so it composes with any app's entity set.\n */\nexport function createDocumentDraftStore(): DocumentDraftStore {\n return {\n async writeDraft({ documentType, documentId, locale, dataPatch, user, tx }) {\n const { drafts } = getContentTables(documentType)\n // No drafts table → the document type isn't publishable; signal the\n // caller to fall back to writing the live row.\n if (!drafts) return false\n\n // Bind the drafts TableClient to the committer's tx so the upsert joins\n // the same SERIALIZABLE transaction as the editor_op insert.\n const client = drafts.makeClient(tx)\n const existing = await client.findOne({ entityId: documentId, locale })\n // Merge onto any existing draft payload so a concurrent form-field draft\n // (saved via saveDraft) isn't clobbered by a block edit, and vice versa.\n const baseData = (existing?.data as Record<string, unknown> | undefined) ?? {}\n const data = { ...baseData, ...dataPatch }\n const now = new Date()\n\n await client.upsert(\n {\n entityId: documentId,\n locale,\n data,\n createdBy: user.id,\n createdByName: user.name ?? null,\n createdAt: now,\n updatedAt: now,\n },\n {\n target: ['entityId', 'locale'],\n // On conflict, only touch the mutable fields — preserve createdBy/createdAt.\n set: { data, updatedAt: now },\n },\n )\n return true\n },\n\n async hasAnyDraft({ documentType, documentId, db }) {\n const { drafts } = getContentTables(documentType)\n if (!drafts) return false\n // Any-locale existence probe — the orphan-draft GC (blocks PR 25)\n // spares a row when ANY locale still carries draft work.\n const client = drafts.makeClient(db)\n return client.exists({ entityId: documentId })\n },\n\n async hasAnyLocaleActivity({ documentType, documentId, db }) {\n // Per-locale publish status (publishDraft on a non-default locale\n // writes HERE, never the base row's status — pr-review H2).\n const { localeStatus } = getContentTables(documentType)\n if (localeStatus) {\n const client = localeStatus.makeClient(db)\n if (await client.exists({ entityId: documentId })) return true\n }\n // Translation rows (saveTranslation writes them directly, no hooks —\n // the base row's updatedAt never moves). Translation tables are\n // generated in @murumets-ee/entity; accessed via schemaRegistry +\n // direct Drizzle, same idiom as translation-status.ts.\n const translationTable = schemaRegistry.get(`${documentType}_translations`)\n if (translationTable) {\n const cols = translationTable as Record<string, PgColumn | undefined>\n const entityIdCol = cols.entityId\n if (entityIdCol !== undefined) {\n const rows = await db\n .select({ one: sql`1` })\n .from(translationTable)\n .where(eq(entityIdCol, documentId))\n .limit(1)\n if (rows.length > 0) return true\n }\n }\n return false\n },\n }\n}\n","/**\n * Draft overlay table schema generator\n *\n * Generates a {entity}_drafts table for entities with the publishable() behavior.\n * Stores a mutable work-in-progress snapshot that non-publishers edit instead of\n * writing directly to the live published row.\n *\n * One draft per entity+locale. Drafts are upserted, discarded, or promoted to live.\n *\n * Uses `defineTable` so ContentClient can operate on drafts through a typed\n * TableClient (see registry.ts). The migration pipeline uses the\n * entity-package generator (which emits FK references) — this one produces a\n * runtime-only table shape for read/write via TableClient.\n */\n\nimport { column, defineTable } from '@murumets-ee/db'\nimport type { Entity } from '@murumets-ee/entity'\n\n/**\n * Generate a `DefinedTable` for a publishable entity's drafts table.\n *\n * Schema:\n * {entity}_drafts (\n * id uuid PK,\n * entity_id uuid NOT NULL,\n * locale varchar(10) NOT NULL DEFAULT '_',\n * data jsonb NOT NULL, -- full entity snapshot\n * created_by varchar(255) NOT NULL, -- user ID (varchar, NOT uuid!)\n * created_by_name varchar(255),\n * created_at timestamptz NOT NULL DEFAULT now(),\n * updated_at timestamptz NOT NULL DEFAULT now(),\n * UNIQUE(entity_id, locale)\n * )\n */\nexport function generateDraftsTable(entity: Entity) {\n return defineTable({\n name: `${entity.name}_drafts`,\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n entityId: column.uuid({ notNull: true, pgName: 'entity_id' }),\n locale: column.varchar({ length: 10, notNull: true, default: '_' }),\n data: column.jsonb<Record<string, unknown>, true>({ notNull: true }),\n createdBy: column.varchar({ length: 255, notNull: true, pgName: 'created_by' }),\n createdByName: column.varchar({ length: 255, pgName: 'created_by_name' }),\n createdAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'created_at',\n }),\n updatedAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'updated_at',\n }),\n },\n unique: [{ on: ['entityId', 'locale'] }],\n })\n}\n\n/** Public type export — the full DefinedTable returned by the generator. */\nexport type DraftsTable = ReturnType<typeof generateDraftsTable>\n","/**\n * Per-locale publish status table schema generator\n *\n * Generates a {entity}_locale_status table for entities that have both\n * publishable() behavior AND translatable fields. This allows different\n * locales to have independent publish statuses.\n *\n * Fallback rule: no row for a locale = inherit base entity status.\n *\n * Uses `defineTable` so ContentClient can operate on rows through a typed\n * TableClient. The migration pipeline uses the entity-package generator\n * (which emits FK references); this one produces a runtime-only table shape\n * for read/write via TableClient.\n */\n\nimport { column, defineTable } from '@murumets-ee/db'\nimport type { Entity } from '@murumets-ee/entity'\n\n/**\n * Generate a `DefinedTable` for a publishable + translatable entity's\n * locale status table.\n *\n * Schema:\n * {entity}_locale_status (\n * id uuid PK,\n * entity_id uuid NOT NULL,\n * locale varchar(10) NOT NULL,\n * status varchar(20) NOT NULL DEFAULT 'draft',\n * published_at timestamptz,\n * UNIQUE(entity_id, locale)\n * )\n */\nexport function generateLocaleStatusTable(entity: Entity) {\n return defineTable({\n name: `${entity.name}_locale_status`,\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n entityId: column.uuid({ notNull: true, pgName: 'entity_id' }),\n locale: column.varchar({ length: 10, notNull: true }),\n status: column.varchar({ length: 20, notNull: true, default: 'draft' }),\n publishedAt: column.timestamp({ withTimezone: true, pgName: 'published_at' }),\n },\n unique: [{ on: ['entityId', 'locale'] }],\n })\n}\n\n/** Public type export — the full DefinedTable returned by the generator. */\nexport type LocaleStatusTable = ReturnType<typeof generateLocaleStatusTable>\n","/**\n * Version history table schema generator\n *\n * Generates a {entity}_versions table for entities with the versionable() behavior.\n * Each version stores a full snapshot + delta (changed fields only).\n *\n * Uses `defineTable` so ContentClient can operate on versions through a typed\n * TableClient. The migration pipeline uses the entity-package generator\n * (which emits FK references) — this one produces a runtime-only table shape\n * for read/write via TableClient.\n */\n\nimport { column, defineTable } from '@murumets-ee/db'\nimport type { Entity } from '@murumets-ee/entity'\n\n/**\n * Generate a `DefinedTable` for a versionable entity's versions table.\n *\n * Schema:\n * {entity}_versions (\n * id uuid PK,\n * entity_id uuid NOT NULL,\n * version integer NOT NULL,\n * locale varchar(10) NOT NULL DEFAULT '_', -- locale this snapshot belongs to\n * data jsonb NOT NULL, -- full snapshot (locale-merged view)\n * delta jsonb, -- only changed fields\n * status varchar(20), -- 'draft' | 'published' at time of save\n * created_by varchar(255),\n * created_by_name varchar(255),\n * created_at timestamptz NOT NULL DEFAULT now(),\n * is_autosave boolean NOT NULL DEFAULT false,\n * UNIQUE(entity_id, version, locale)\n * )\n *\n * Each locale has its own version sequence. The default locale uses '_'\n * as the locale value (distinguishable from actual locale codes).\n */\nexport function generateVersionsTable(entity: Entity) {\n return defineTable({\n name: `${entity.name}_versions`,\n columns: {\n id: column.uuid({ primaryKey: true, defaultRandom: true }),\n entityId: column.uuid({ notNull: true, pgName: 'entity_id' }),\n version: column.integer({ notNull: true }),\n locale: column.varchar({ length: 10, notNull: true, default: '_' }),\n data: column.jsonb<Record<string, unknown>, true>({ notNull: true }),\n delta: column.jsonb<Record<string, unknown>>(),\n status: column.varchar({ length: 20 }),\n createdBy: column.varchar({ length: 255, pgName: 'created_by' }),\n createdByName: column.varchar({ length: 255, pgName: 'created_by_name' }),\n createdAt: column.timestamp({\n notNull: true,\n defaultNow: true,\n withTimezone: true,\n pgName: 'created_at',\n }),\n isAutosave: column.boolean({ notNull: true, default: false, pgName: 'is_autosave' }),\n },\n unique: [{ on: ['entityId', 'version', 'locale'] }],\n })\n}\n\n/** Public type export — the full DefinedTable returned by the generator. */\nexport type VersionsTable = ReturnType<typeof generateVersionsTable>\n","/**\n * Content plugin — registers locale configuration and version schemas.\n *\n * Usage:\n * import { content } from '@murumets-ee/content/plugin'\n *\n * export default defineLumiConfig({\n * plugins: [\n * content({\n * locales: [\n * { code: 'en', label: 'English' },\n * { code: 'de', label: 'German' },\n * ],\n * defaultLocale: 'en',\n * }),\n * ],\n * })\n */\n\nimport { definePlugin, type TrashAutoEnablePolicy } from '@murumets-ee/core'\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { createDocumentDraftStore } from './document-draft-store.js'\nimport { setActiveLocaleStatusWriterFactory } from './locale-status-slot.js'\nimport { generateDraftsTable } from './schema/drafts.js'\nimport { generateLocaleStatusTable } from './schema/locale-status.js'\nimport { toolkitContentLocks } from './schema/locks.js'\nimport { registerContentTables } from './schema/registry.js'\nimport { generateVersionsTable } from './schema/versions.js'\nimport type { ContentConfig } from './types.js'\n\n/**\n * The active config lives on `globalThis` keyed by `Symbol.for(...)`, NOT in\n * a module-level binding — same Turbopack-dev module-duplication hazard as\n * the content-tables registry one file over (see schema/registry.ts): a\n * lazily-compiled route chunk can get a SECOND instance of this module\n * whose `let` was never assigned, and `getContentConfig()` throws \"not\n * initialized\" on a perfectly initialized app. `Symbol.for` keys are\n * process-global, so every duplicate instance reads the same slot.\n * (pr-review pass-1 M4 — load-bearing on the publish + locale-routing path.)\n */\nconst CONFIG_KEY = Symbol.for('@murumets-ee/content:content-config')\n\ninterface GlobalWithContentConfig {\n [CONFIG_KEY]?: ContentConfig\n}\n\n/**\n * Get the content configuration. Throws if plugin not initialized.\n */\nexport function getContentConfig(): ContentConfig {\n const config = (globalThis as GlobalWithContentConfig)[CONFIG_KEY]\n if (!config) {\n throw new Error(\n '@murumets-ee/content plugin not initialized. Add content() to your plugins array.',\n )\n }\n return config\n}\n\n/**\n * Content plugin factory.\n */\nexport function content(config: ContentConfig) {\n // Validate config\n if (!config.locales || config.locales.length === 0) {\n throw new Error('content() requires at least one locale')\n }\n\n if (!config.defaultLocale) {\n throw new Error('content() requires a defaultLocale')\n }\n\n const defaultExists = config.locales.some((l) => l.code === config.defaultLocale)\n if (!defaultExists) {\n throw new Error(\n `defaultLocale \"${config.defaultLocale}\" not found in locales: [${config.locales.map((l) => l.code).join(', ')}]`,\n )\n }\n\n // plan/trash SD002 — declare the auto-enable policy for the framework to\n // apply at boot (createApp injects `trashable()` into publishable entities\n // BEFORE schema-gen). Declarative, so it can't be forgotten and carries no\n // plugin-array-order hazard. `autoTrash` defaults ON.\n const autoTrash = config.autoTrash ?? true\n const trashPolicy: TrashAutoEnablePolicy | undefined =\n autoTrash === false\n ? undefined\n : {\n publishable: true,\n ...(typeof autoTrash === 'object' && autoTrash.exclude\n ? { exclude: autoTrash.exclude }\n : {}),\n }\n\n return definePlugin({\n name: '@murumets-ee/content',\n // SD015 — contribute the draft store the block editor's op-commit route\n // writes through. blocks owns the slot; content owns the {entity}_drafts\n // tables. Stateless; resolves the per-entity drafts table at call time.\n contributes: {\n 'blocks.documentDraftStore': createDocumentDraftStore(),\n // SD002 — only contribute when auto-trash is enabled (omitted → no policy\n // → createApp's applyTrashAutoEnable is a no-op for content).\n ...(trashPolicy ? { 'trash.autoEnable': trashPolicy } : {}),\n },\n server: {\n // The shared locks table is registered declaratively via the\n // framework's merge step (#469). The per-entity tables below\n // (versions / layout / drafts / locale_status) are generated\n // dynamically from `app.entities` and stay imperatively\n // registered — they're not known at plugin-construct time.\n tables: { toolkitContentLocks },\n init: async (app) => {\n // Store config globally\n ;(globalThis as GlobalWithContentConfig)[CONFIG_KEY] = config\n\n let versionableCount = 0\n let localeStatusCount = 0\n let draftsCount = 0\n\n for (const [name, entity] of app.entities) {\n // Register version tables for versionable entities\n const hasVersionable = entity.behaviors?.some(\n (b: { name: string }) => b.name === 'versionable',\n )\n if (hasVersionable) {\n const versionsTableName = `${name}_versions`\n if (!schemaRegistry.has(versionsTableName)) {\n const versions = generateVersionsTable(entity)\n schemaRegistry.register(versionsTableName, versions.table)\n registerContentTables(name, { versions })\n }\n versionableCount++\n }\n\n // Register locale_status tables for publishable + translatable entities\n const isPublishable = entity.behaviors?.some(\n (b: { name: string }) => b.name === 'publishable',\n )\n const allFields = entity.allFields as Record<string, { translatable?: boolean }>\n const hasTranslatable = Object.values(allFields).some((f) => f.translatable)\n if (isPublishable && hasTranslatable) {\n const statusTableName = `${name}_locale_status`\n if (!schemaRegistry.has(statusTableName)) {\n const localeStatus = generateLocaleStatusTable(entity)\n schemaRegistry.register(statusTableName, localeStatus.table)\n registerContentTables(name, { localeStatus })\n }\n localeStatusCount++\n }\n\n // Register drafts table for publishable entities\n if (isPublishable) {\n const draftsTableName = `${name}_drafts`\n if (!schemaRegistry.has(draftsTableName)) {\n const drafts = generateDraftsTable(entity)\n schemaRegistry.register(draftsTableName, drafts.table)\n registerContentTables(name, { drafts })\n }\n draftsCount++\n }\n }\n\n // Expose the per-locale status writer to AdminClient.updateForLocale\n // (SD021). Must run AFTER the locale_status registration loop above —\n // the factory consults that registry per entity. This is capability\n // injection via a globalThis slot (queue's enqueue-on-commit\n // precedent), not contribution registration — no merge-step involved.\n setActiveLocaleStatusWriterFactory(config.locales.map((l) => l.code))\n\n app.logger.info(\n {\n locales: config.locales.map((l) => l.code),\n defaultLocale: config.defaultLocale,\n versionableEntities: versionableCount,\n localeStatusEntities: localeStatusCount,\n draftEntities: draftsCount,\n },\n 'Content plugin initialized',\n )\n },\n },\n })\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-yRLOOCOn.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;AAQA;;UAAiB,YAAA;EACf,IAAA;EACA,KAAK;AAAA;AAAA,UAGU,aAAA;EACf,OAAA,EAAS,YAAY;EACrB,aAAA;EADA;;;;;;AAe+B;AAOjC;;;;AAIU;AAOV;EAlBE,SAAA;IAAwB,OAAA;EAAA;AAAA;AAAA,UAOT,kBAAA;EAiBA;EAff,SAAA;EAmBW;EAjBX,QAAQ;AAAA;AAAA,UAOO,YAAA,eAA2B,MAAA,oBAA0B,MAAA;EACpE,EAAA;EACA,QAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA,EAAM,KAAA;EACN,KAAA,EAAO,OAAA,CAAQ,KAAA;EACf,MAAA;EACA,SAAA;EACA,aAAA;EACA,SAAA,EAAW,IAAA;EACX,UAAA;AAAA;AAAA,UAae,sBAAA;EACf,QAAA;EACA,KAAA;EACA,UAAA;EACA,OAAA;AAAA;AAAA,UAGe,iBAAA;EAAA,CACd,MAAA,WAAiB,sBAAsB;AAAA;AAAA,UAGzB,iBAAA;EACf,KAAA;EACA,QAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,UAOe,mBAAA;EACf,MAAA;EACA,MAAA;EACA,WAAA,EAAa,IAAI;AAAA;AAAA,UAOF,UAAA,eAAyB,MAAA,oBAA0B,MAAA;EAClE,EAAA;EACA,QAAA;EACA,MAAA;EACA,IAAA,EAAM,KAAA;EACN,SAAA;EACA,aAAA;EACA,SAAA,EAAW,IAAA;EACX,SAAA,EAAW,IAAA;AAAA;AAAA,UAOI,QAAA;EACf,QAAA;EACA,YAAA;EACA,QAAA,EAAU,IAAA;EACV,SAAA,EAAW,IAAI;AAAA"}
|