From 8dc7f9b8342f0e98a7babd60b246c45ce313a443 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Fri, 23 Jan 2026 16:02:09 -0500 Subject: [PATCH] fix some lexicon validation stuff --- .../functions/migrate_user_to_standard.ts | 78 +++++++------------ lexicons/api/lexicons.ts | 4 +- .../api/types/site/standard/publication.ts | 2 +- lexicons/site/standard/publication.json | 4 +- lexicons/src/normalize.ts | 45 +++++++++-- 5 files changed, 75 insertions(+), 58 deletions(-) diff --git a/app/api/inngest/functions/migrate_user_to_standard.ts b/app/api/inngest/functions/migrate_user_to_standard.ts index 7c630e42..cf809b64 100644 --- a/app/api/inngest/functions/migrate_user_to_standard.ts +++ b/app/api/inngest/functions/migrate_user_to_standard.ts @@ -109,10 +109,11 @@ export const migrate_user_to_standard = inngest.createFunction( }) .filter((x) => x !== null); - // Run all PDS writes in parallel - const pubPdsResults = await Promise.all( - publicationsToMigrate.map(({ pub, rkey, newRecord }) => - step.run(`pds-write-publication-${pub.uri}`, async () => { + // Run PDS + DB writes together for each publication + const pubResults = await Promise.all( + publicationsToMigrate.map(({ pub, rkey, normalized, newRecord }) => + step.run(`migrate-publication-${pub.uri}`, async () => { + // PDS write const agent = await createAuthenticatedAgent(did); const putResult = await agent.com.atproto.repo.putRecord({ repo: did, @@ -121,16 +122,9 @@ export const migrate_user_to_standard = inngest.createFunction( record: newRecord, validate: false, }); - return { oldUri: pub.uri, newUri: putResult.data.uri }; - }), - ), - ); + const newUri = putResult.data.uri; - // Run all DB writes in parallel - const pubDbResults = await Promise.all( - publicationsToMigrate.map(({ pub, normalized, newRecord }, index) => { - const newUri = pubPdsResults[index].newUri; - return step.run(`db-write-publication-${pub.uri}`, async () => { + // DB write const { error: dbError } = await supabaseServerClient .from("publications") .upsert({ @@ -149,12 +143,12 @@ export const migrate_user_to_standard = inngest.createFunction( }; } return { success: true as const, oldUri: pub.uri, newUri }; - }); - }), + }), + ), ); // Process results - for (const result of pubDbResults) { + for (const result of pubResults) { if (result.success) { publicationUriMap[result.oldUri] = result.newUri; stats.publicationsMigrated++; @@ -239,7 +233,7 @@ export const migrate_user_to_standard = inngest.createFunction( $type: "site.standard.document", title: normalized.title || "Untitled", site: siteValue, - path: rkey, + path: "/" + rkey, publishedAt: normalized.publishedAt || new Date().toISOString(), description: normalized.description, content: normalized.content, @@ -252,10 +246,11 @@ export const migrate_user_to_standard = inngest.createFunction( }) .filter((x) => x !== null); - // Run all PDS writes in parallel - const docPdsResults = await Promise.all( - documentsToMigrate.map(({ doc, rkey, newRecord }) => - step.run(`pds-write-document-${doc.uri}`, async () => { + // Run PDS + DB writes together for each document + const docResults = await Promise.all( + documentsToMigrate.map(({ doc, rkey, newRecord, oldPubUri }) => + step.run(`migrate-document-${doc.uri}`, async () => { + // PDS write const agent = await createAuthenticatedAgent(did); const putResult = await agent.com.atproto.repo.putRecord({ repo: did, @@ -264,16 +259,9 @@ export const migrate_user_to_standard = inngest.createFunction( record: newRecord, validate: false, }); - return { oldUri: doc.uri, newUri: putResult.data.uri }; - }), - ), - ); + const newUri = putResult.data.uri; - // Run all DB writes in parallel - const docDbResults = await Promise.all( - documentsToMigrate.map(({ doc, newRecord, oldPubUri }, index) => { - const newUri = docPdsResults[index].newUri; - return step.run(`db-write-document-${doc.uri}`, async () => { + // DB write const { error: dbError } = await supabaseServerClient .from("documents") .upsert({ @@ -302,12 +290,12 @@ export const migrate_user_to_standard = inngest.createFunction( } return { success: true as const, oldUri: doc.uri, newUri }; - }); - }), + }), + ), ); // Process results - for (const result of docDbResults) { + for (const result of docResults) { if (result.success) { documentUriMap[result.oldUri] = result.newUri; stats.documentsMigrated++; @@ -428,10 +416,11 @@ export const migrate_user_to_standard = inngest.createFunction( }) .filter((x) => x !== null); - // Run all PDS writes in parallel - const subPdsResults = await Promise.all( + // Run PDS + DB writes together for each subscription + const subResults = await Promise.all( subscriptionsToMigrate.map(({ sub, rkey, newRecord }) => - step.run(`pds-write-subscription-${sub.uri}`, async () => { + step.run(`migrate-subscription-${sub.uri}`, async () => { + // PDS write const agent = await createAuthenticatedAgent(did); const putResult = await agent.com.atproto.repo.putRecord({ repo: did, @@ -440,16 +429,9 @@ export const migrate_user_to_standard = inngest.createFunction( record: newRecord, validate: false, }); - return { oldUri: sub.uri, newUri: putResult.data.uri }; - }), - ), - ); + const newUri = putResult.data.uri; - // Run all DB writes in parallel - const subDbResults = await Promise.all( - subscriptionsToMigrate.map(({ sub, newRecord }, index) => { - const newUri = subPdsResults[index].newUri; - return step.run(`db-write-subscription-${sub.uri}`, async () => { + // DB write const { error: dbError } = await supabaseServerClient .from("publication_subscriptions") .update({ @@ -467,12 +449,12 @@ export const migrate_user_to_standard = inngest.createFunction( }; } return { success: true as const, oldUri: sub.uri, newUri }; - }); - }), + }), + ), ); // Process results - for (const result of subDbResults) { + for (const result of subResults) { if (result.success) { userSubscriptionUriMap[result.oldUri] = result.newUri; stats.userSubscriptionsMigrated++; diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts index 409eb985..5ef436e0 100644 --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -2215,8 +2215,8 @@ export const schemaDict = { type: 'ref', }, theme: { - type: 'ref', - ref: 'lex:pub.leaflet.publication#theme', + type: 'union', + refs: ['lex:pub.leaflet.publication#theme'], }, description: { maxGraphemes: 300, diff --git a/lexicons/api/types/site/standard/publication.ts b/lexicons/api/types/site/standard/publication.ts index 88f3b671..2daf12b6 100644 --- a/lexicons/api/types/site/standard/publication.ts +++ b/lexicons/api/types/site/standard/publication.ts @@ -15,7 +15,7 @@ const id = 'site.standard.publication' export interface Record { $type: 'site.standard.publication' basicTheme?: SiteStandardThemeBasic.Main - theme?: PubLeafletPublication.Theme + theme?: $Typed | { $type: string } description?: string icon?: BlobRef name: string diff --git a/lexicons/site/standard/publication.json b/lexicons/site/standard/publication.json index e1c3aea5..b1449052 100644 --- a/lexicons/site/standard/publication.json +++ b/lexicons/site/standard/publication.json @@ -9,8 +9,8 @@ "type": "ref" }, "theme": { - "type": "ref", - "ref": "pub.leaflet.publication#theme" + "type": "union", + "refs": ["pub.leaflet.publication#theme"] }, "description": { "maxGraphemes": 300, diff --git a/lexicons/src/normalize.ts b/lexicons/src/normalize.ts index 9b642c3f..d4890e07 100644 --- a/lexicons/src/normalize.ts +++ b/lexicons/src/normalize.ts @@ -14,7 +14,7 @@ */ import type * as PubLeafletDocument from "../api/types/pub/leaflet/document"; -import type * as PubLeafletPublication from "../api/types/pub/leaflet/publication"; +import * as PubLeafletPublication from "../api/types/pub/leaflet/publication"; import type * as PubLeafletContent from "../api/types/pub/leaflet/content"; import type * as SiteStandardDocument from "../api/types/site/standard/document"; import type * as SiteStandardPublication from "../api/types/site/standard/publication"; @@ -31,7 +31,20 @@ export type NormalizedDocument = SiteStandardDocument.Record & { }; // Normalized publication type - uses the generated site.standard.publication type -export type NormalizedPublication = SiteStandardPublication.Record; +// with the theme narrowed to only the valid pub.leaflet.publication#theme type +// (isTheme validates that $type is present, so we use $Typed) +// Note: We explicitly list fields rather than using Omit because the generated Record type +// has an index signature [k: string]: unknown that interferes with property typing +export type NormalizedPublication = { + $type: "site.standard.publication"; + name: string; + url: string; + description?: string; + icon?: SiteStandardPublication.Record["icon"]; + basicTheme?: SiteStandardThemeBasic.Main; + theme?: $Typed; + preferences?: SiteStandardPublication.Preferences; +}; /** * Checks if the record is a pub.leaflet.document @@ -210,9 +223,16 @@ export function normalizePublication( ): NormalizedPublication | null { if (!record || typeof record !== "object") return null; - // Pass through site.standard records directly + // Pass through site.standard records directly, but validate the theme if (isStandardPublication(record)) { - return record; + // Validate theme - only keep if it's a valid pub.leaflet.publication#theme + const theme = PubLeafletPublication.isTheme(record.theme) + ? (record.theme as $Typed) + : undefined; + return { + ...record, + theme, + }; } if (isLeafletPublication(record)) { @@ -225,6 +245,21 @@ export function normalizePublication( const basicTheme = leafletThemeToBasicTheme(record.theme); + // Validate theme - only keep if it's a valid pub.leaflet.publication#theme with $type set + // For legacy records without $type, add it during normalization + let theme: $Typed | undefined; + if (record.theme) { + if (PubLeafletPublication.isTheme(record.theme)) { + theme = record.theme as $Typed; + } else { + // Legacy theme without $type - add it + theme = { + ...record.theme, + $type: "pub.leaflet.publication#theme", + }; + } + } + // Convert preferences to site.standard format (strip/replace $type) const preferences: SiteStandardPublication.Preferences | undefined = record.preferences @@ -243,7 +278,7 @@ export function normalizePublication( description: record.description, icon: record.icon, basicTheme, - theme: record.theme, + theme, preferences, }; } -- 2.51.2