From 271659156949593fa233cefae65a4f2cd00f73eb Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 13 Jan 2026 16:32:09 -0500 Subject: [PATCH] add basic site.standard stuff and normalization function --- appview/index.ts | 93 ++++ drizzle/relations.ts | 71 +-- drizzle/schema.ts | 71 +-- lexicons/api/index.ts | 303 ++++++++++++ lexicons/api/lexicons.ts | 271 +++++++++++ lexicons/api/types/pub/leaflet/content.ts | 33 ++ lexicons/api/types/site/standard/document.ts | 41 ++ .../types/site/standard/graph/subscription.ts | 31 ++ .../api/types/site/standard/publication.ts | 53 +++ .../api/types/site/standard/theme/basic.ts | 34 ++ .../api/types/site/standard/theme/color.ts | 50 ++ lexicons/build.ts | 2 + lexicons/pub/leaflet/content.json | 27 ++ lexicons/site/standard/document.json | 68 +++ .../site/standard/graph/subscription.json | 23 + lexicons/site/standard/publication.json | 68 +++ lexicons/site/standard/theme/basic.json | 41 ++ lexicons/site/standard/theme/color.json | 53 +++ lexicons/src/content.ts | 29 ++ lexicons/src/normalize.ts | 262 +++++++++++ package-lock.json | 432 ++++++++++++++++-- package.json | 3 +- patterns/lexicons.md | 284 ++++++++++++ supabase/database.types.ts | 144 +++++- 24 files changed, 2395 insertions(+), 92 deletions(-) create mode 100644 lexicons/api/types/pub/leaflet/content.ts create mode 100644 lexicons/api/types/site/standard/document.ts create mode 100644 lexicons/api/types/site/standard/graph/subscription.ts create mode 100644 lexicons/api/types/site/standard/publication.ts create mode 100644 lexicons/api/types/site/standard/theme/basic.ts create mode 100644 lexicons/api/types/site/standard/theme/color.ts create mode 100644 lexicons/pub/leaflet/content.json create mode 100644 lexicons/site/standard/document.json create mode 100644 lexicons/site/standard/graph/subscription.json create mode 100644 lexicons/site/standard/publication.json create mode 100644 lexicons/site/standard/theme/basic.json create mode 100644 lexicons/site/standard/theme/color.json create mode 100644 lexicons/src/content.ts create mode 100644 lexicons/src/normalize.ts create mode 100644 patterns/lexicons.md diff --git a/appview/index.ts b/appview/index.ts index 25c38037..31b0f0c2 100644 --- a/appview/index.ts +++ b/appview/index.ts @@ -11,6 +11,9 @@ import { PubLeafletComment, PubLeafletPollVote, PubLeafletPollDefinition, + SiteStandardDocument, + SiteStandardPublication, + SiteStandardGraphSubscription, } from "lexicons/api"; import { AppBskyEmbedExternal, @@ -47,6 +50,9 @@ async function main() { ids.PubLeafletPollDefinition, // ids.AppBskyActorProfile, "app.bsky.feed.post", + ids.SiteStandardDocument, + ids.SiteStandardPublication, + ids.SiteStandardGraphSubscription, ], handleEvent, onError: (err) => { @@ -225,6 +231,93 @@ async function handleEvent(evt: Event) { .eq("uri", evt.uri.toString()); } } + if (evt.collection === ids.SiteStandardDocument) { + if (evt.event === "create" || evt.event === "update") { + let record = SiteStandardDocument.validateRecord(evt.record); + if (!record.success) { + console.log(record.error); + return; + } + await supabase + .from("identities") + .upsert({ atp_did: evt.did }, { onConflict: "atp_did" }); + let docResult = await supabase.from("site_standard_documents").upsert({ + uri: evt.uri.toString(), + data: record.value as Json, + identity_did: evt.did, + }); + if (docResult.error) console.log(docResult.error); + if (record.value.site) { + let siteURI = new AtUri(record.value.site); + + if (siteURI.host !== evt.uri.host) { + console.log("Unauthorized to create document in site!"); + return; + } + let docInPublicationResult = await supabase + .from("site_standard_documents_in_publications") + .upsert({ + publication: record.value.site, + document: evt.uri.toString(), + }); + await supabase + .from("site_standard_documents_in_publications") + .delete() + .neq("publication", record.value.site) + .eq("document", evt.uri.toString()); + + if (docInPublicationResult.error) + console.log(docInPublicationResult.error); + } + } + if (evt.event === "delete") { + await supabase + .from("site_standard_documents") + .delete() + .eq("uri", evt.uri.toString()); + } + } + if (evt.collection === ids.SiteStandardPublication) { + if (evt.event === "create" || evt.event === "update") { + let record = SiteStandardPublication.validateRecord(evt.record); + if (!record.success) return; + await supabase + .from("identities") + .upsert({ atp_did: evt.did }, { onConflict: "atp_did" }); + await supabase.from("site_standard_publications").upsert({ + uri: evt.uri.toString(), + identity_did: evt.did, + data: record.value as Json, + }); + } + if (evt.event === "delete") { + await supabase + .from("site_standard_publications") + .delete() + .eq("uri", evt.uri.toString()); + } + } + if (evt.collection === ids.SiteStandardGraphSubscription) { + if (evt.event === "create" || evt.event === "update") { + let record = SiteStandardGraphSubscription.validateRecord(evt.record); + if (!record.success) return; + await supabase + .from("identities") + .upsert({ atp_did: evt.did }, { onConflict: "atp_did" }); + await supabase.from("site_standard_subscriptions").upsert({ + uri: evt.uri.toString(), + identity: evt.did, + publication: record.value.publication, + record: record.value as Json, + }); + } + if (evt.event === "delete") { + await supabase + .from("site_standard_subscriptions") + .delete() + .eq("uri", evt.uri.toString()); + } + } // if (evt.collection === ids.AppBskyActorProfile) { // //only listen to updates because we should fetch it for the first time when they subscribe! // if (evt.event === "update") { diff --git a/drizzle/relations.ts b/drizzle/relations.ts index 1e08d4fe..7c933dbf 100644 --- a/drizzle/relations.ts +++ b/drizzle/relations.ts @@ -1,5 +1,5 @@ import { relations } from "drizzle-orm/relations"; -import { identities, notifications, publications, documents, comments_on_documents, bsky_profiles, entity_sets, entities, facts, email_auth_tokens, poll_votes_on_entity, permission_tokens, phone_rsvps_to_entity, custom_domains, custom_domain_routes, email_subscriptions_to_entity, atp_poll_records, atp_poll_votes, bsky_follows, subscribers_to_publications, permission_token_on_homepage, documents_in_publications, document_mentions_in_bsky, bsky_posts, publication_domains, leaflets_in_publications, publication_subscriptions, permission_token_rights } from "./schema"; +import { identities, notifications, publications, documents, comments_on_documents, bsky_profiles, entity_sets, entities, facts, email_auth_tokens, poll_votes_on_entity, permission_tokens, phone_rsvps_to_entity, custom_domains, custom_domain_routes, email_subscriptions_to_entity, atp_poll_records, atp_poll_votes, bsky_follows, subscribers_to_publications, documents_in_publications, document_mentions_in_bsky, bsky_posts, permission_token_on_homepage, publication_domains, publication_subscriptions, leaflets_to_documents, permission_token_rights, leaflets_in_publications } from "./schema"; export const notificationsRelations = relations(notifications, ({one}) => ({ identity: one(identities, { @@ -43,8 +43,8 @@ export const publicationsRelations = relations(publications, ({one, many}) => ({ subscribers_to_publications: many(subscribers_to_publications), documents_in_publications: many(documents_in_publications), publication_domains: many(publication_domains), - leaflets_in_publications: many(leaflets_in_publications), publication_subscriptions: many(publication_subscriptions), + leaflets_in_publications: many(leaflets_in_publications), })); export const comments_on_documentsRelations = relations(comments_on_documents, ({one}) => ({ @@ -62,6 +62,7 @@ export const documentsRelations = relations(documents, ({many}) => ({ comments_on_documents: many(comments_on_documents), documents_in_publications: many(documents_in_publications), document_mentions_in_bskies: many(document_mentions_in_bsky), + leaflets_to_documents: many(leaflets_to_documents), leaflets_in_publications: many(leaflets_in_publications), })); @@ -136,8 +137,9 @@ export const permission_tokensRelations = relations(permission_tokens, ({one, ma }), email_subscriptions_to_entities: many(email_subscriptions_to_entity), permission_token_on_homepages: many(permission_token_on_homepage), - leaflets_in_publications: many(leaflets_in_publications), + leaflets_to_documents: many(leaflets_to_documents), permission_token_rights: many(permission_token_rights), + leaflets_in_publications: many(leaflets_in_publications), })); export const phone_rsvps_to_entityRelations = relations(phone_rsvps_to_entity, ({one}) => ({ @@ -225,17 +227,6 @@ export const subscribers_to_publicationsRelations = relations(subscribers_to_pub }), })); -export const permission_token_on_homepageRelations = relations(permission_token_on_homepage, ({one}) => ({ - identity: one(identities, { - fields: [permission_token_on_homepage.identity], - references: [identities.id] - }), - permission_token: one(permission_tokens, { - fields: [permission_token_on_homepage.token], - references: [permission_tokens.id] - }), -})); - export const documents_in_publicationsRelations = relations(documents_in_publications, ({one}) => ({ document: one(documents, { fields: [documents_in_publications.document], @@ -262,6 +253,17 @@ export const bsky_postsRelations = relations(bsky_posts, ({many}) => ({ document_mentions_in_bskies: many(document_mentions_in_bsky), })); +export const permission_token_on_homepageRelations = relations(permission_token_on_homepage, ({one}) => ({ + identity: one(identities, { + fields: [permission_token_on_homepage.identity], + references: [identities.id] + }), + permission_token: one(permission_tokens, { + fields: [permission_token_on_homepage.token], + references: [permission_tokens.id] + }), +})); + export const publication_domainsRelations = relations(publication_domains, ({one}) => ({ custom_domain: one(custom_domains, { fields: [publication_domains.domain], @@ -277,21 +279,6 @@ export const publication_domainsRelations = relations(publication_domains, ({one }), })); -export const leaflets_in_publicationsRelations = relations(leaflets_in_publications, ({one}) => ({ - document: one(documents, { - fields: [leaflets_in_publications.doc], - references: [documents.uri] - }), - permission_token: one(permission_tokens, { - fields: [leaflets_in_publications.leaflet], - references: [permission_tokens.id] - }), - publication: one(publications, { - fields: [leaflets_in_publications.publication], - references: [publications.uri] - }), -})); - export const publication_subscriptionsRelations = relations(publication_subscriptions, ({one}) => ({ identity: one(identities, { fields: [publication_subscriptions.identity], @@ -303,6 +290,17 @@ export const publication_subscriptionsRelations = relations(publication_subscrip }), })); +export const leaflets_to_documentsRelations = relations(leaflets_to_documents, ({one}) => ({ + document: one(documents, { + fields: [leaflets_to_documents.document], + references: [documents.uri] + }), + permission_token: one(permission_tokens, { + fields: [leaflets_to_documents.leaflet], + references: [permission_tokens.id] + }), +})); + export const permission_token_rightsRelations = relations(permission_token_rights, ({one}) => ({ entity_set: one(entity_sets, { fields: [permission_token_rights.entity_set], @@ -312,4 +310,19 @@ export const permission_token_rightsRelations = relations(permission_token_right fields: [permission_token_rights.token], references: [permission_tokens.id] }), +})); + +export const leaflets_in_publicationsRelations = relations(leaflets_in_publications, ({one}) => ({ + document: one(documents, { + fields: [leaflets_in_publications.doc], + references: [documents.uri] + }), + permission_token: one(permission_tokens, { + fields: [leaflets_in_publications.leaflet], + references: [permission_tokens.id] + }), + publication: one(publications, { + fields: [leaflets_in_publications.publication], + references: [publications.uri] + }), })); \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 153b82e6..171a551e 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -136,7 +136,7 @@ export const permission_tokens = pgTable("permission_tokens", { export const identities = pgTable("identities", { id: uuid("id").defaultRandom().primaryKey().notNull(), created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), - home_page: uuid("home_page").notNull().references(() => permission_tokens.id, { onDelete: "cascade" } ), + home_page: uuid("home_page").default(sql`create_identity_homepage()`).notNull().references(() => permission_tokens.id, { onDelete: "cascade" } ), email: text("email"), atp_did: text("atp_did"), interface_state: jsonb("interface_state"), @@ -260,17 +260,6 @@ export const subscribers_to_publications = pgTable("subscribers_to_publications" } }); -export const permission_token_on_homepage = pgTable("permission_token_on_homepage", { - token: uuid("token").notNull().references(() => permission_tokens.id, { onDelete: "cascade" } ), - identity: uuid("identity").notNull().references(() => identities.id, { onDelete: "cascade" } ), - created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), -}, -(table) => { - return { - permission_token_creator_pkey: primaryKey({ columns: [table.token, table.identity], name: "permission_token_creator_pkey"}), - } -}); - export const documents_in_publications = pgTable("documents_in_publications", { publication: text("publication").notNull().references(() => publications.uri, { onDelete: "cascade" } ), document: text("document").notNull().references(() => documents.uri, { onDelete: "cascade" } ), @@ -295,31 +284,28 @@ export const document_mentions_in_bsky = pgTable("document_mentions_in_bsky", { } }); -export const publication_domains = pgTable("publication_domains", { - publication: text("publication").notNull().references(() => publications.uri, { onDelete: "cascade" } ), - domain: text("domain").notNull().references(() => custom_domains.domain, { onDelete: "cascade" } ), +export const permission_token_on_homepage = pgTable("permission_token_on_homepage", { + token: uuid("token").notNull().references(() => permission_tokens.id, { onDelete: "cascade", onUpdate: "cascade" } ), + identity: uuid("identity").notNull().references(() => identities.id, { onDelete: "cascade" } ), created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), - identity: text("identity").notNull().references(() => identities.atp_did, { onDelete: "cascade", onUpdate: "cascade" } ), + archived: boolean("archived"), }, (table) => { return { - publication_idx: index("publication_domains_publication_idx").on(table.publication), - publication_domains_pkey: primaryKey({ columns: [table.publication, table.domain], name: "publication_domains_pkey"}), + permission_token_creator_pkey: primaryKey({ columns: [table.token, table.identity], name: "permission_token_creator_pkey"}), } }); -export const leaflets_in_publications = pgTable("leaflets_in_publications", { +export const publication_domains = pgTable("publication_domains", { publication: text("publication").notNull().references(() => publications.uri, { onDelete: "cascade" } ), - doc: text("doc").default('').references(() => documents.uri, { onDelete: "set null" } ), - leaflet: uuid("leaflet").notNull().references(() => permission_tokens.id, { onDelete: "cascade" } ), - description: text("description").default('').notNull(), - title: text("title").default('').notNull(), + domain: text("domain").notNull().references(() => custom_domains.domain, { onDelete: "cascade" } ), + created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), + identity: text("identity").notNull().references(() => identities.atp_did, { onDelete: "cascade", onUpdate: "cascade" } ), }, (table) => { return { - leaflet_idx: index("leaflets_in_publications_leaflet_idx").on(table.leaflet), - publication_idx: index("leaflets_in_publications_publication_idx").on(table.publication), - leaflets_in_publications_pkey: primaryKey({ columns: [table.publication, table.leaflet], name: "leaflets_in_publications_pkey"}), + publication_idx: index("publication_domains_publication_idx").on(table.publication), + publication_domains_pkey: primaryKey({ columns: [table.publication, table.domain], name: "publication_domains_pkey"}), } }); @@ -338,6 +324,21 @@ export const publication_subscriptions = pgTable("publication_subscriptions", { } }); +export const leaflets_to_documents = pgTable("leaflets_to_documents", { + leaflet: uuid("leaflet").notNull().references(() => permission_tokens.id, { onDelete: "cascade", onUpdate: "cascade" } ), + document: text("document").notNull().references(() => documents.uri, { onDelete: "cascade", onUpdate: "cascade" } ), + created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), + title: text("title").default('').notNull(), + description: text("description").default('').notNull(), + tags: text("tags").default('RRAY[').array(), + cover_image: text("cover_image"), +}, +(table) => { + return { + leaflets_to_documents_pkey: primaryKey({ columns: [table.leaflet, table.document], name: "leaflets_to_documents_pkey"}), + } +}); + export const permission_token_rights = pgTable("permission_token_rights", { token: uuid("token").notNull().references(() => permission_tokens.id, { onDelete: "cascade", onUpdate: "cascade" } ), entity_set: uuid("entity_set").notNull().references(() => entity_sets.id, { onDelete: "cascade", onUpdate: "cascade" } ), @@ -353,4 +354,22 @@ export const permission_token_rights = pgTable("permission_token_rights", { entity_set_idx: index("permission_token_rights_entity_set_idx").on(table.entity_set), permission_token_rights_pkey: primaryKey({ columns: [table.token, table.entity_set], name: "permission_token_rights_pkey"}), } +}); + +export const leaflets_in_publications = pgTable("leaflets_in_publications", { + publication: text("publication").notNull().references(() => publications.uri, { onDelete: "cascade" } ), + doc: text("doc").default('').references(() => documents.uri, { onDelete: "set null" } ), + leaflet: uuid("leaflet").notNull().references(() => permission_tokens.id, { onDelete: "cascade", onUpdate: "cascade" } ), + description: text("description").default('').notNull(), + title: text("title").default('').notNull(), + archived: boolean("archived"), + tags: text("tags").default('RRAY[').array(), + cover_image: text("cover_image"), +}, +(table) => { + return { + leaflet_idx: index("leaflets_in_publications_leaflet_idx").on(table.leaflet), + publication_idx: index("leaflets_in_publications_publication_idx").on(table.publication), + leaflets_in_publications_pkey: primaryKey({ columns: [table.publication, table.leaflet], name: "leaflets_in_publications_pkey"}), + } }); \ No newline at end of file diff --git a/lexicons/api/index.ts b/lexicons/api/index.ts index b225cb7d..fd28a572 100644 --- a/lexicons/api/index.ts +++ b/lexicons/api/index.ts @@ -38,6 +38,7 @@ import * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text' import * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList' import * as PubLeafletBlocksWebsite from './types/pub/leaflet/blocks/website' import * as PubLeafletComment from './types/pub/leaflet/comment' +import * as PubLeafletContent from './types/pub/leaflet/content' import * as PubLeafletDocument from './types/pub/leaflet/document' import * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' import * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' @@ -48,6 +49,11 @@ import * as PubLeafletPublication from './types/pub/leaflet/publication' import * as PubLeafletRichtextFacet from './types/pub/leaflet/richtext/facet' import * as PubLeafletThemeBackgroundImage from './types/pub/leaflet/theme/backgroundImage' import * as PubLeafletThemeColor from './types/pub/leaflet/theme/color' +import * as SiteStandardDocument from './types/site/standard/document' +import * as SiteStandardGraphSubscription from './types/site/standard/graph/subscription' +import * as SiteStandardPublication from './types/site/standard/publication' +import * as SiteStandardThemeBasic from './types/site/standard/theme/basic' +import * as SiteStandardThemeColor from './types/site/standard/theme/color' export * as AppBskyActorProfile from './types/app/bsky/actor/profile' export * as ComAtprotoLabelDefs from './types/com/atproto/label/defs' @@ -78,6 +84,7 @@ export * as PubLeafletBlocksText from './types/pub/leaflet/blocks/text' export * as PubLeafletBlocksUnorderedList from './types/pub/leaflet/blocks/unorderedList' export * as PubLeafletBlocksWebsite from './types/pub/leaflet/blocks/website' export * as PubLeafletComment from './types/pub/leaflet/comment' +export * as PubLeafletContent from './types/pub/leaflet/content' export * as PubLeafletDocument from './types/pub/leaflet/document' export * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' export * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' @@ -88,6 +95,11 @@ export * as PubLeafletPublication from './types/pub/leaflet/publication' export * as PubLeafletRichtextFacet from './types/pub/leaflet/richtext/facet' export * as PubLeafletThemeBackgroundImage from './types/pub/leaflet/theme/backgroundImage' export * as PubLeafletThemeColor from './types/pub/leaflet/theme/color' +export * as SiteStandardDocument from './types/site/standard/document' +export * as SiteStandardGraphSubscription from './types/site/standard/graph/subscription' +export * as SiteStandardPublication from './types/site/standard/publication' +export * as SiteStandardThemeBasic from './types/site/standard/theme/basic' +export * as SiteStandardThemeColor from './types/site/standard/theme/color' export const PUB_LEAFLET_PAGES = { CanvasTextAlignLeft: 'pub.leaflet.pages.canvas#textAlignLeft', @@ -106,12 +118,14 @@ export class AtpBaseClient extends XrpcClient { app: AppNS com: ComNS pub: PubNS + site: SiteNS constructor(options: FetchHandler | FetchHandlerOptions) { super(options, schemas) this.app = new AppNS(this) this.com = new ComNS(this) this.pub = new PubNS(this) + this.site = new SiteNS(this) } /** @deprecated use `this` instead */ @@ -952,3 +966,292 @@ export class PubLeafletPublicationRecord { ) } } + +export class SiteNS { + _client: XrpcClient + standard: SiteStandardNS + + constructor(client: XrpcClient) { + this._client = client + this.standard = new SiteStandardNS(client) + } +} + +export class SiteStandardNS { + _client: XrpcClient + document: SiteStandardDocumentRecord + publication: SiteStandardPublicationRecord + graph: SiteStandardGraphNS + theme: SiteStandardThemeNS + + constructor(client: XrpcClient) { + this._client = client + this.graph = new SiteStandardGraphNS(client) + this.theme = new SiteStandardThemeNS(client) + this.document = new SiteStandardDocumentRecord(client) + this.publication = new SiteStandardPublicationRecord(client) + } +} + +export class SiteStandardGraphNS { + _client: XrpcClient + subscription: SiteStandardGraphSubscriptionRecord + + constructor(client: XrpcClient) { + this._client = client + this.subscription = new SiteStandardGraphSubscriptionRecord(client) + } +} + +export class SiteStandardGraphSubscriptionRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: SiteStandardGraphSubscription.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'site.standard.graph.subscription', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ + uri: string + cid: string + value: SiteStandardGraphSubscription.Record + }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'site.standard.graph.subscription', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.graph.subscription' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async put( + params: OmitKey< + ComAtprotoRepoPutRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.graph.subscription' + const res = await this._client.call( + 'com.atproto.repo.putRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'site.standard.graph.subscription', ...params }, + { headers }, + ) + } +} + +export class SiteStandardThemeNS { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } +} + +export class SiteStandardDocumentRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: SiteStandardDocument.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'site.standard.document', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ uri: string; cid: string; value: SiteStandardDocument.Record }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'site.standard.document', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.document' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async put( + params: OmitKey< + ComAtprotoRepoPutRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.document' + const res = await this._client.call( + 'com.atproto.repo.putRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'site.standard.document', ...params }, + { headers }, + ) + } +} + +export class SiteStandardPublicationRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: SiteStandardPublication.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'site.standard.publication', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ + uri: string + cid: string + value: SiteStandardPublication.Record + }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'site.standard.publication', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.publication' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async put( + params: OmitKey< + ComAtprotoRepoPutRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'site.standard.publication' + const res = await this._client.call( + 'com.atproto.repo.putRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'site.standard.publication', ...params }, + { headers }, + ) + } +} diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts index 6d0acd58..50315dc1 100644 --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -1400,6 +1400,31 @@ export const schemaDict = { }, }, }, + PubLeafletContent: { + lexicon: 1, + id: 'pub.leaflet.content', + revision: 1, + description: 'A lexicon for long form rich media documents', + defs: { + main: { + type: 'object', + description: 'Content format for leaflet documents', + required: ['pages'], + properties: { + pages: { + type: 'array', + items: { + type: 'union', + refs: [ + 'lex:pub.leaflet.pages.linearDocument', + 'lex:pub.leaflet.pages.canvas', + ], + }, + }, + }, + }, + }, + }, PubLeafletDocument: { lexicon: 1, id: 'pub.leaflet.document', @@ -2082,6 +2107,246 @@ export const schemaDict = { }, }, }, + SiteStandardDocument: { + defs: { + main: { + key: 'tid', + record: { + properties: { + bskyPostRef: { + ref: 'lex:com.atproto.repo.strongRef', + type: 'ref', + }, + content: { + closed: false, + refs: ['lex:pub.leaflet.content'], + type: 'union', + }, + coverImage: { + accept: ['image/*'], + maxSize: 1000000, + type: 'blob', + }, + description: { + maxGraphemes: 300, + maxLength: 3000, + type: 'string', + }, + path: { + description: + 'combine with the publication url or the document site to construct a full url to the document', + type: 'string', + }, + publishedAt: { + format: 'datetime', + type: 'string', + }, + site: { + description: + 'URI to the site or publication this document belongs to (https or at-uri)', + format: 'uri', + type: 'string', + }, + tags: { + items: { + maxGraphemes: 50, + maxLength: 100, + type: 'string', + }, + type: 'array', + }, + textContent: { + type: 'string', + }, + title: { + maxGraphemes: 128, + maxLength: 1280, + type: 'string', + }, + updatedAt: { + format: 'datetime', + type: 'string', + }, + }, + required: ['site', 'title', 'publishedAt'], + type: 'object', + }, + type: 'record', + }, + }, + id: 'site.standard.document', + lexicon: 1, + }, + SiteStandardGraphSubscription: { + defs: { + main: { + description: 'Record declaring a subscription to a publication', + key: 'tid', + record: { + properties: { + publication: { + format: 'at-uri', + type: 'string', + }, + }, + required: ['publication'], + type: 'object', + }, + type: 'record', + }, + }, + id: 'site.standard.graph.subscription', + lexicon: 1, + }, + SiteStandardPublication: { + defs: { + main: { + key: 'tid', + record: { + properties: { + basicTheme: { + ref: 'lex:site.standard.theme.basic', + type: 'ref', + }, + theme: { + type: 'ref', + ref: 'lex:pub.leaflet.publication#theme', + }, + description: { + maxGraphemes: 300, + maxLength: 3000, + type: 'string', + }, + icon: { + accept: ['image/*'], + maxSize: 1000000, + type: 'blob', + }, + name: { + maxGraphemes: 128, + maxLength: 1280, + type: 'string', + }, + preferences: { + ref: 'lex:site.standard.publication#preferences', + type: 'ref', + }, + url: { + format: 'uri', + type: 'string', + }, + }, + required: ['url', 'name'], + type: 'object', + }, + type: 'record', + }, + preferences: { + properties: { + showInDiscover: { + default: true, + type: 'boolean', + }, + showComments: { + default: true, + type: 'boolean', + }, + showMentions: { + default: true, + type: 'boolean', + }, + showPrevNext: { + default: false, + type: 'boolean', + }, + }, + type: 'object', + }, + }, + id: 'site.standard.publication', + lexicon: 1, + }, + SiteStandardThemeBasic: { + defs: { + main: { + properties: { + accent: { + refs: ['lex:site.standard.theme.color#rgb'], + type: 'union', + }, + accentForeground: { + refs: ['lex:site.standard.theme.color#rgb'], + type: 'union', + }, + background: { + refs: ['lex:site.standard.theme.color#rgb'], + type: 'union', + }, + foreground: { + refs: ['lex:site.standard.theme.color#rgb'], + type: 'union', + }, + }, + required: ['background', 'foreground', 'accent', 'accentForeground'], + type: 'object', + }, + }, + id: 'site.standard.theme.basic', + lexicon: 1, + }, + SiteStandardThemeColor: { + lexicon: 1, + id: 'site.standard.theme.color', + defs: { + rgb: { + type: 'object', + required: ['r', 'g', 'b'], + properties: { + r: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + g: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + b: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + }, + }, + rgba: { + type: 'object', + required: ['r', 'g', 'b', 'a'], + properties: { + r: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + g: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + b: { + type: 'integer', + minimum: 0, + maximum: 255, + }, + a: { + type: 'integer', + minimum: 0, + maximum: 100, + }, + }, + }, + }, + }, } as const satisfies Record export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) @@ -2144,6 +2409,7 @@ export const ids = { PubLeafletBlocksUnorderedList: 'pub.leaflet.blocks.unorderedList', PubLeafletBlocksWebsite: 'pub.leaflet.blocks.website', PubLeafletComment: 'pub.leaflet.comment', + PubLeafletContent: 'pub.leaflet.content', PubLeafletDocument: 'pub.leaflet.document', PubLeafletGraphSubscription: 'pub.leaflet.graph.subscription', PubLeafletPagesCanvas: 'pub.leaflet.pages.canvas', @@ -2154,4 +2420,9 @@ export const ids = { PubLeafletRichtextFacet: 'pub.leaflet.richtext.facet', PubLeafletThemeBackgroundImage: 'pub.leaflet.theme.backgroundImage', PubLeafletThemeColor: 'pub.leaflet.theme.color', + SiteStandardDocument: 'site.standard.document', + SiteStandardGraphSubscription: 'site.standard.graph.subscription', + SiteStandardPublication: 'site.standard.publication', + SiteStandardThemeBasic: 'site.standard.theme.basic', + SiteStandardThemeColor: 'site.standard.theme.color', } as const diff --git a/lexicons/api/types/pub/leaflet/content.ts b/lexicons/api/types/pub/leaflet/content.ts new file mode 100644 index 00000000..1dba8ce6 --- /dev/null +++ b/lexicons/api/types/pub/leaflet/content.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../lexicons' +import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' +import type * as PubLeafletPagesLinearDocument from './pages/linearDocument' +import type * as PubLeafletPagesCanvas from './pages/canvas' + +const is$typed = _is$typed, + validate = _validate +const id = 'pub.leaflet.content' + +/** Content format for leaflet documents */ +export interface Main { + $type?: 'pub.leaflet.content' + pages: ( + | $Typed + | $Typed + | { $type: string } + )[] +} + +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) +} + +export function validateMain(v: V) { + return validate
(v, id, hashMain) +} diff --git a/lexicons/api/types/site/standard/document.ts b/lexicons/api/types/site/standard/document.ts new file mode 100644 index 00000000..ee23656b --- /dev/null +++ b/lexicons/api/types/site/standard/document.ts @@ -0,0 +1,41 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../lexicons' +import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' +import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef' +import type * as PubLeafletContent from '../../pub/leaflet/content' + +const is$typed = _is$typed, + validate = _validate +const id = 'site.standard.document' + +export interface Record { + $type: 'site.standard.document' + bskyPostRef?: ComAtprotoRepoStrongRef.Main + content?: $Typed | { $type: string } + coverImage?: BlobRef + description?: string + /** combine with the publication url or the document site to construct a full url to the document */ + path?: string + publishedAt: string + /** URI to the site or publication this document belongs to (https or at-uri) */ + site: string + tags?: string[] + textContent?: string + title: string + updatedAt?: string + [k: string]: unknown +} + +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) +} + +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) +} diff --git a/lexicons/api/types/site/standard/graph/subscription.ts b/lexicons/api/types/site/standard/graph/subscription.ts new file mode 100644 index 00000000..56a065a2 --- /dev/null +++ b/lexicons/api/types/site/standard/graph/subscription.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { + type $Typed, + is$typed as _is$typed, + type OmitKey, +} from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'site.standard.graph.subscription' + +export interface Record { + $type: 'site.standard.graph.subscription' + publication: string + [k: string]: unknown +} + +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) +} + +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) +} diff --git a/lexicons/api/types/site/standard/publication.ts b/lexicons/api/types/site/standard/publication.ts new file mode 100644 index 00000000..88f3b671 --- /dev/null +++ b/lexicons/api/types/site/standard/publication.ts @@ -0,0 +1,53 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../lexicons' +import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' +import type * as SiteStandardThemeBasic from './theme/basic' +import type * as PubLeafletPublication from '../../pub/leaflet/publication' + +const is$typed = _is$typed, + validate = _validate +const id = 'site.standard.publication' + +export interface Record { + $type: 'site.standard.publication' + basicTheme?: SiteStandardThemeBasic.Main + theme?: PubLeafletPublication.Theme + description?: string + icon?: BlobRef + name: string + preferences?: Preferences + url: string + [k: string]: unknown +} + +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) +} + +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) +} + +export interface Preferences { + $type?: 'site.standard.publication#preferences' + showInDiscover: boolean + showComments: boolean + showMentions: boolean + showPrevNext: boolean +} + +const hashPreferences = 'preferences' + +export function isPreferences(v: V) { + return is$typed(v, id, hashPreferences) +} + +export function validatePreferences(v: V) { + return validate(v, id, hashPreferences) +} diff --git a/lexicons/api/types/site/standard/theme/basic.ts b/lexicons/api/types/site/standard/theme/basic.ts new file mode 100644 index 00000000..72e6cd68 --- /dev/null +++ b/lexicons/api/types/site/standard/theme/basic.ts @@ -0,0 +1,34 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { + type $Typed, + is$typed as _is$typed, + type OmitKey, +} from '../../../../util' +import type * as SiteStandardThemeColor from './color' + +const is$typed = _is$typed, + validate = _validate +const id = 'site.standard.theme.basic' + +export interface Main { + $type?: 'site.standard.theme.basic' + accent: $Typed | { $type: string } + accentForeground: $Typed | { $type: string } + background: $Typed | { $type: string } + foreground: $Typed | { $type: string } +} + +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) +} + +export function validateMain(v: V) { + return validate
(v, id, hashMain) +} diff --git a/lexicons/api/types/site/standard/theme/color.ts b/lexicons/api/types/site/standard/theme/color.ts new file mode 100644 index 00000000..baac4487 --- /dev/null +++ b/lexicons/api/types/site/standard/theme/color.ts @@ -0,0 +1,50 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { + type $Typed, + is$typed as _is$typed, + type OmitKey, +} from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'site.standard.theme.color' + +export interface Rgb { + $type?: 'site.standard.theme.color#rgb' + r: number + g: number + b: number +} + +const hashRgb = 'rgb' + +export function isRgb(v: V) { + return is$typed(v, id, hashRgb) +} + +export function validateRgb(v: V) { + return validate(v, id, hashRgb) +} + +export interface Rgba { + $type?: 'site.standard.theme.color#rgba' + r: number + g: number + b: number + a: number +} + +const hashRgba = 'rgba' + +export function isRgba(v: V) { + return is$typed(v, id, hashRgba) +} + +export function validateRgba(v: V) { + return validate(v, id, hashRgba) +} diff --git a/lexicons/build.ts b/lexicons/build.ts index d9fc5faa..1b5fcc29 100644 --- a/lexicons/build.ts +++ b/lexicons/build.ts @@ -10,6 +10,7 @@ import * as path from "path"; import { PubLeafletRichTextFacet } from "./src/facet"; import { PubLeafletComment } from "./src/comment"; import { PubLeafletAuthFullPermissions } from "./src/authFullPermissions"; +import { PubLeafletContent } from "./src/content"; const outdir = path.join("lexicons", "pub", "leaflet"); @@ -20,6 +21,7 @@ fs.mkdirSync(outdir, { recursive: true }); const lexicons = [ PubLeafletDocument, + PubLeafletContent, PubLeafletComment, PubLeafletRichTextFacet, PubLeafletAuthFullPermissions, diff --git a/lexicons/pub/leaflet/content.json b/lexicons/pub/leaflet/content.json new file mode 100644 index 00000000..7978c068 --- /dev/null +++ b/lexicons/pub/leaflet/content.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "pub.leaflet.content", + "revision": 1, + "description": "A lexicon for long form rich media documents", + "defs": { + "main": { + "type": "object", + "description": "Content format for leaflet documents", + "required": [ + "pages" + ], + "properties": { + "pages": { + "type": "array", + "items": { + "type": "union", + "refs": [ + "pub.leaflet.pages.linearDocument", + "pub.leaflet.pages.canvas" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/lexicons/site/standard/document.json b/lexicons/site/standard/document.json new file mode 100644 index 00000000..8503b7ad --- /dev/null +++ b/lexicons/site/standard/document.json @@ -0,0 +1,68 @@ +{ + "defs": { + "main": { + "key": "tid", + "record": { + "properties": { + "bskyPostRef": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "content": { + "closed": false, + "refs": ["pub.leaflet.content"], + "type": "union" + }, + "coverImage": { + "accept": ["image/*"], + "maxSize": 1000000, + "type": "blob" + }, + "description": { + "maxGraphemes": 300, + "maxLength": 3000, + "type": "string" + }, + "path": { + "description": "combine with the publication url or the document site to construct a full url to the document", + "type": "string" + }, + "publishedAt": { + "format": "datetime", + "type": "string" + }, + "site": { + "description": "URI to the site or publication this document belongs to (https or at-uri)", + "format": "uri", + "type": "string" + }, + "tags": { + "items": { + "maxGraphemes": 50, + "maxLength": 100, + "type": "string" + }, + "type": "array" + }, + "textContent": { + "type": "string" + }, + "title": { + "maxGraphemes": 128, + "maxLength": 1280, + "type": "string" + }, + "updatedAt": { + "format": "datetime", + "type": "string" + } + }, + "required": ["site", "title", "publishedAt"], + "type": "object" + }, + "type": "record" + } + }, + "id": "site.standard.document", + "lexicon": 1 +} diff --git a/lexicons/site/standard/graph/subscription.json b/lexicons/site/standard/graph/subscription.json new file mode 100644 index 00000000..79f1302c --- /dev/null +++ b/lexicons/site/standard/graph/subscription.json @@ -0,0 +1,23 @@ +{ + "defs": { + "main": { + "description": "Record declaring a subscription to a publication", + "key": "tid", + "record": { + "properties": { + "publication": { + "format": "at-uri", + "type": "string" + } + }, + "required": [ + "publication" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "site.standard.graph.subscription", + "lexicon": 1 +} diff --git a/lexicons/site/standard/publication.json b/lexicons/site/standard/publication.json new file mode 100644 index 00000000..e1c3aea5 --- /dev/null +++ b/lexicons/site/standard/publication.json @@ -0,0 +1,68 @@ +{ + "defs": { + "main": { + "key": "tid", + "record": { + "properties": { + "basicTheme": { + "ref": "site.standard.theme.basic", + "type": "ref" + }, + "theme": { + "type": "ref", + "ref": "pub.leaflet.publication#theme" + }, + "description": { + "maxGraphemes": 300, + "maxLength": 3000, + "type": "string" + }, + "icon": { + "accept": ["image/*"], + "maxSize": 1000000, + "type": "blob" + }, + "name": { + "maxGraphemes": 128, + "maxLength": 1280, + "type": "string" + }, + "preferences": { + "ref": "#preferences", + "type": "ref" + }, + "url": { + "format": "uri", + "type": "string" + } + }, + "required": ["url", "name"], + "type": "object" + }, + "type": "record" + }, + "preferences": { + "properties": { + "showInDiscover": { + "default": true, + "type": "boolean" + }, + "showComments": { + "default": true, + "type": "boolean" + }, + "showMentions": { + "default": true, + "type": "boolean" + }, + "showPrevNext": { + "default": false, + "type": "boolean" + } + }, + "type": "object" + } + }, + "id": "site.standard.publication", + "lexicon": 1 +} diff --git a/lexicons/site/standard/theme/basic.json b/lexicons/site/standard/theme/basic.json new file mode 100644 index 00000000..4e79ae33 --- /dev/null +++ b/lexicons/site/standard/theme/basic.json @@ -0,0 +1,41 @@ +{ + "defs": { + "main": { + "properties": { + "accent": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union" + }, + "accentForeground": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union" + }, + "background": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union" + }, + "foreground": { + "refs": [ + "site.standard.theme.color#rgb" + ], + "type": "union" + } + }, + "required": [ + "background", + "foreground", + "accent", + "accentForeground" + ], + "type": "object" + } + }, + "id": "site.standard.theme.basic", + "lexicon": 1 +} diff --git a/lexicons/site/standard/theme/color.json b/lexicons/site/standard/theme/color.json new file mode 100644 index 00000000..9fdba770 --- /dev/null +++ b/lexicons/site/standard/theme/color.json @@ -0,0 +1,53 @@ +{ + "lexicon": 1, + "id": "site.standard.theme.color", + "defs": { + "rgb": { + "type": "object", + "required": ["r", "g", "b"], + "properties": { + "r": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "g": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "b": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + } + }, + "rgba": { + "type": "object", + "required": ["r", "g", "b", "a"], + "properties": { + "r": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "g": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "b": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "a": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } +} diff --git a/lexicons/src/content.ts b/lexicons/src/content.ts new file mode 100644 index 00000000..4c8c7895 --- /dev/null +++ b/lexicons/src/content.ts @@ -0,0 +1,29 @@ +import { LexiconDoc } from "@atproto/lexicon"; +import { PubLeafletPagesLinearDocument } from "./pages/LinearDocument"; +import { PubLeafletPagesCanvasDocument } from "./pages"; + +export const PubLeafletContent: LexiconDoc = { + lexicon: 1, + id: "pub.leaflet.content", + revision: 1, + description: "A lexicon for long form rich media documents", + defs: { + main: { + type: "object", + description: "Content format for leaflet documents", + required: ["pages"], + properties: { + pages: { + type: "array", + items: { + type: "union", + refs: [ + PubLeafletPagesLinearDocument.id, + PubLeafletPagesCanvasDocument.id, + ], + }, + }, + }, + }, + }, +}; diff --git a/lexicons/src/normalize.ts b/lexicons/src/normalize.ts new file mode 100644 index 00000000..bfb43a21 --- /dev/null +++ b/lexicons/src/normalize.ts @@ -0,0 +1,262 @@ +/** + * Normalization utilities for converting between pub.leaflet and site.standard lexicon formats. + * + * The standard format (site.standard.*) is used as the canonical representation for + * reading data from the database, while both formats are accepted for storage. + */ + +import type * as PubLeafletDocument from "../api/types/pub/leaflet/document"; +import type * 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"; +import type * as SiteStandardThemeBasic from "../api/types/site/standard/theme/basic"; +import type * as PubLeafletThemeColor from "../api/types/pub/leaflet/theme/color"; +import type { $Typed } from "../api/util"; + +// Normalized document type - uses the generated site.standard.document type +// with an additional optional theme field for backwards compatibility +export type NormalizedDocument = SiteStandardDocument.Record & { + // Keep the original theme for components that need leaflet-specific styling + theme?: PubLeafletPublication.Theme; +}; + +// Normalized publication type - uses the generated site.standard.publication type +export type NormalizedPublication = SiteStandardPublication.Record; + +/** + * Checks if the record is a pub.leaflet.document + */ +export function isLeafletDocument( + record: unknown +): record is PubLeafletDocument.Record { + if (!record || typeof record !== "object") return false; + const r = record as Record; + return ( + r.$type === "pub.leaflet.document" || + // Legacy records without $type but with pages array + (Array.isArray(r.pages) && typeof r.author === "string") + ); +} + +/** + * Checks if the record is a site.standard.document + */ +export function isStandardDocument( + record: unknown +): record is SiteStandardDocument.Record { + if (!record || typeof record !== "object") return false; + const r = record as Record; + return r.$type === "site.standard.document"; +} + +/** + * Checks if the record is a pub.leaflet.publication + */ +export function isLeafletPublication( + record: unknown +): record is PubLeafletPublication.Record { + if (!record || typeof record !== "object") return false; + const r = record as Record; + return ( + r.$type === "pub.leaflet.publication" || + // Legacy records without $type but with name and no url + (typeof r.name === "string" && !("url" in r)) + ); +} + +/** + * Checks if the record is a site.standard.publication + */ +export function isStandardPublication( + record: unknown +): record is SiteStandardPublication.Record { + if (!record || typeof record !== "object") return false; + const r = record as Record; + return r.$type === "site.standard.publication"; +} + +/** + * Extracts RGB values from a color union type + */ +function extractRgb( + color: + | $Typed + | $Typed + | { $type: string } + | undefined +): { r: number; g: number; b: number } | undefined { + if (!color || typeof color !== "object") return undefined; + const c = color as Record; + if ( + typeof c.r === "number" && + typeof c.g === "number" && + typeof c.b === "number" + ) { + return { r: c.r, g: c.g, b: c.b }; + } + return undefined; +} + +/** + * Converts a pub.leaflet theme to a site.standard.theme.basic format + */ +export function leafletThemeToBasicTheme( + theme: PubLeafletPublication.Theme | undefined +): SiteStandardThemeBasic.Main | undefined { + if (!theme) return undefined; + + const background = extractRgb(theme.backgroundColor); + const accent = extractRgb(theme.accentBackground) || extractRgb(theme.primary); + const accentForeground = extractRgb(theme.accentText); + + // If we don't have the required colors, return undefined + if (!background || !accent) return undefined; + + // Default foreground to dark if not specified + const foreground = { r: 0, g: 0, b: 0 }; + + // Default accent foreground to white if not specified + const finalAccentForeground = accentForeground || { r: 255, g: 255, b: 255 }; + + return { + $type: "site.standard.theme.basic", + background: { $type: "site.standard.theme.color#rgb", ...background }, + foreground: { $type: "site.standard.theme.color#rgb", ...foreground }, + accent: { $type: "site.standard.theme.color#rgb", ...accent }, + accentForeground: { + $type: "site.standard.theme.color#rgb", + ...finalAccentForeground, + }, + }; +} + +/** + * Normalizes a document record from either format to the standard format. + * + * @param record - The document record from the database (either pub.leaflet or site.standard) + * @returns A normalized document in site.standard format, or null if invalid/unrecognized + */ +export function normalizeDocument(record: unknown): NormalizedDocument | null { + if (!record || typeof record !== "object") return null; + + // Pass through site.standard records directly + if (isStandardDocument(record)) { + return record as NormalizedDocument; + } + + if (isLeafletDocument(record)) { + // Convert from pub.leaflet to site.standard + const site = record.publication; + const publishedAt = record.publishedAt; + + if (!site || !publishedAt) { + return null; + } + + // Wrap pages in pub.leaflet.content structure + const content: $Typed | undefined = record.pages + ? { + $type: "pub.leaflet.content" as const, + pages: record.pages, + } + : undefined; + + return { + $type: "site.standard.document", + title: record.title, + site, + publishedAt, + description: record.description, + tags: record.tags, + coverImage: record.coverImage, + bskyPostRef: record.postRef, + content, + theme: record.theme, + }; + } + + return null; +} + +/** + * Normalizes a publication record from either format to the standard format. + * + * @param record - The publication record from the database (either pub.leaflet or site.standard) + * @returns A normalized publication in site.standard format, or null if invalid/unrecognized + */ +export function normalizePublication( + record: unknown +): NormalizedPublication | null { + if (!record || typeof record !== "object") return null; + + // Pass through site.standard records directly + if (isStandardPublication(record)) { + return record; + } + + if (isLeafletPublication(record)) { + // Convert from pub.leaflet to site.standard + const url = record.base_path ? `https://${record.base_path}` : undefined; + + if (!url) { + return null; + } + + const basicTheme = leafletThemeToBasicTheme(record.theme); + + // Convert preferences to site.standard format (strip/replace $type) + const preferences: SiteStandardPublication.Preferences | undefined = + record.preferences + ? { + showInDiscover: record.preferences.showInDiscover, + showComments: record.preferences.showComments, + showMentions: record.preferences.showMentions, + showPrevNext: record.preferences.showPrevNext, + } + : undefined; + + return { + $type: "site.standard.publication", + name: record.name, + url, + description: record.description, + icon: record.icon, + basicTheme, + theme: record.theme, + preferences, + }; + } + + return null; +} + +/** + * Type guard to check if a normalized document has leaflet content + */ +export function hasLeafletContent( + doc: NormalizedDocument +): doc is NormalizedDocument & { + content: $Typed; +} { + return ( + doc.content !== undefined && + (doc.content as { $type?: string }).$type === "pub.leaflet.content" + ); +} + +/** + * Gets the pages array from a normalized document, handling both formats + */ +export function getDocumentPages( + doc: NormalizedDocument +): PubLeafletContent.Main["pages"] | undefined { + if (!doc.content) return undefined; + + if (hasLeafletContent(doc)) { + return doc.content.pages; + } + + // Unknown content type + return undefined; +} diff --git a/package-lock.json b/package-lock.json index bbf3a8cd..65a85d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@atproto/oauth-client-node": "^0.3.8", "@atproto/sync": "^0.1.34", "@atproto/syntax": "^0.3.3", + "@atproto/tap": "^0.1.1", "@atproto/xrpc": "^0.7.5", "@atproto/xrpc-server": "^0.9.5", "@hono/node-server": "^1.14.3", @@ -264,23 +265,16 @@ } }, "node_modules/@atproto/common-web": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.3.tgz", - "integrity": "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.10.tgz", + "integrity": "sha512-TLDZSgSKzT8ZgOrBrTGK87J1CXve9TEuY6NVVUBRkOMzRRtQzpFb9/ih5WVS/hnaWVvE30CfuyaetRoma+WKNw==", "license": "MIT", "dependencies": { - "graphemer": "^1.4.0", - "multiformats": "^9.9.0", - "uint8arrays": "3.0.0", + "@atproto/lex-data": "0.0.6", + "@atproto/lex-json": "0.0.6", "zod": "^3.23.8" } }, - "node_modules/@atproto/common-web/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, "node_modules/@atproto/common/node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", @@ -288,9 +282,9 @@ "license": "(Apache-2.0 AND MIT)" }, "node_modules/@atproto/crypto": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.4.tgz", - "integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.5.tgz", + "integrity": "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw==", "license": "MIT", "dependencies": { "@noble/curves": "^1.7.0", @@ -360,6 +354,92 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/@atproto/lex": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@atproto/lex/-/lex-0.0.9.tgz", + "integrity": "sha512-o6gauf1lz0iyzJR0rqSj4VHOrO+Nt8+/iPb0KPojw1ieXk13zOSTSxotAoDzO/dP6y8Ey5jxwuCQGuzab/4XnQ==", + "license": "MIT", + "dependencies": { + "@atproto/lex-builder": "0.0.9", + "@atproto/lex-client": "0.0.7", + "@atproto/lex-data": "0.0.6", + "@atproto/lex-installer": "0.0.9", + "@atproto/lex-json": "0.0.6", + "@atproto/lex-schema": "0.0.7", + "tslib": "^2.8.1", + "yargs": "^17.0.0" + }, + "bin": { + "lex": "bin/lex", + "ts-lex": "bin/lex" + } + }, + "node_modules/@atproto/lex-builder": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@atproto/lex-builder/-/lex-builder-0.0.9.tgz", + "integrity": "sha512-buOFk1JpuW3twI7To7f/67zQQ1NulLHf/oasH/kTOPUAd0dNyeAa13t9eRSVGbwi0BcZYxRxBm0QzPmdLKyuyw==", + "license": "MIT", + "dependencies": { + "@atproto/lex-document": "0.0.8", + "@atproto/lex-schema": "0.0.7", + "prettier": "^3.2.5", + "ts-morph": "^27.0.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-builder/node_modules/@ts-morph/common": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", + "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", + "license": "MIT", + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@atproto/lex-builder/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@atproto/lex-builder/node_modules/ts-morph": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", + "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.28.1", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/@atproto/lex-cbor": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@atproto/lex-cbor/-/lex-cbor-0.0.6.tgz", + "integrity": "sha512-lee2T00owDy3I1plRHuURT6f98NIpYZZr2wXa5pJZz5JzefZ+nv8gJ2V70C2f+jmSG+5S9NTIy4uJw94vaHf4A==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "0.0.6", + "multiformats": "^9.9.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-cbor/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, "node_modules/@atproto/lex-cli": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.5.tgz", @@ -390,6 +470,149 @@ "dev": true, "license": "MIT" }, + "node_modules/@atproto/lex-client": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@atproto/lex-client/-/lex-client-0.0.7.tgz", + "integrity": "sha512-ofUz3yXJ0nN/M9aqqF2ZUL/4D1wWT1P4popCfV3OEDsDrtWofMflYPFz1IWuyPa2e83paaEHRhaw3bZEhgXH1w==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "0.0.6", + "@atproto/lex-json": "0.0.6", + "@atproto/lex-schema": "0.0.7", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-data": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.6.tgz", + "integrity": "sha512-MBNB4ghRJQzuXK1zlUPljpPbQcF1LZ5dzxy274KqPt4p3uPuRw0mHjgcCoWzRUNBQC685WMQR4IN9DHtsnG57A==", + "license": "MIT", + "dependencies": { + "@atproto/syntax": "0.4.2", + "multiformats": "^9.9.0", + "tslib": "^2.8.1", + "uint8arrays": "3.0.0", + "unicode-segmenter": "^0.14.0" + } + }, + "node_modules/@atproto/lex-data/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, + "node_modules/@atproto/lex-data/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/@atproto/lex-document": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@atproto/lex-document/-/lex-document-0.0.8.tgz", + "integrity": "sha512-p3l5h96Hx0vxUwbO/eas6x5h2vU0JVN1a/ktX4k3PlK9YLXfWMFsv+RdVwVZom8o0irHwlcyh1D/cY0PyUojDA==", + "license": "MIT", + "dependencies": { + "@atproto/lex-schema": "0.0.7", + "core-js": "^3", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-installer": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@atproto/lex-installer/-/lex-installer-0.0.9.tgz", + "integrity": "sha512-zEeIeSaSCb3j+zNsqqMY7+X5FO6fxy/MafaCEj42KsXQHNcobuygZsnG/0fxMj/kMvhjrNUCp/w9PyOMwx4hQg==", + "license": "MIT", + "dependencies": { + "@atproto/lex-builder": "0.0.9", + "@atproto/lex-cbor": "0.0.6", + "@atproto/lex-data": "0.0.6", + "@atproto/lex-document": "0.0.8", + "@atproto/lex-resolver": "0.0.8", + "@atproto/lex-schema": "0.0.7", + "@atproto/syntax": "0.4.2", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-installer/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, + "node_modules/@atproto/lex-json": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.6.tgz", + "integrity": "sha512-EILnN5cditPvf+PCNjXt7reMuzjugxAL1fpSzmzJbEMGMUwxOf5pPWxRsaA/M3Boip4NQZ+6DVrPOGUMlnqceg==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "0.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-resolver": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@atproto/lex-resolver/-/lex-resolver-0.0.8.tgz", + "integrity": "sha512-4hXT560+k5BIttouuhXOr+UkhAuFvvkJaVdqYb8vx2Ez7eHPiZ+yWkUK6FKpyGsx2whHkJzgleEA6DNWtdDlWA==", + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "0.2.5", + "@atproto/crypto": "0.4.5", + "@atproto/lex-client": "0.0.7", + "@atproto/lex-data": "0.0.6", + "@atproto/lex-document": "0.0.8", + "@atproto/lex-schema": "0.0.7", + "@atproto/repo": "0.8.12", + "@atproto/syntax": "0.4.2", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-resolver/node_modules/@atproto-labs/did-resolver": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.5.tgz", + "integrity": "sha512-he7EC6OMSifNs01a4RT9mta/yYitoKDzlK9ty2TFV5Uj/+HpB4vYMRdIDFrRW0Hcsehy90E2t/dw0t7361MEKQ==", + "license": "MIT", + "dependencies": { + "@atproto-labs/fetch": "0.2.3", + "@atproto-labs/pipe": "0.1.1", + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.2.4", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/lex-resolver/node_modules/@atproto/did": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.4.tgz", + "integrity": "sha512-nxNiCgXeo7pfjojq9fpfZxCO0X0xUipNVKW+AHNZwQKiUDt6zYL0VXEfm8HBUwQOCmKvj2pRRSM1Cur+tUWk3g==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/lex-resolver/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, + "node_modules/@atproto/lex-schema": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@atproto/lex-schema/-/lex-schema-0.0.7.tgz", + "integrity": "sha512-/7HkTUsnP1rlzmVE6nnY0kl/hydL/W8V29V8BhFwdAvdDKpYcdRgzzsMe38LAt+ZOjHknRCZDIKGsbQMSbJErw==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "0.0.6", + "@atproto/syntax": "0.4.2", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-schema/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, "node_modules/@atproto/lexicon": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.1.tgz", @@ -472,15 +695,15 @@ } }, "node_modules/@atproto/repo": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.8.9.tgz", - "integrity": "sha512-FTePZS2KEv8++pkOB8GGvm46V6uJqd/95bPA1cXTDXyw0cqeVEOItfxkCH1ky/fY71QYr0NkmqMUwuwZ/gwEtQ==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@atproto/repo/-/repo-0.8.12.tgz", + "integrity": "sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ==", "license": "MIT", "dependencies": { - "@atproto/common": "^0.4.12", - "@atproto/common-web": "^0.4.3", - "@atproto/crypto": "^0.4.4", - "@atproto/lexicon": "^0.5.1", + "@atproto/common": "^0.5.3", + "@atproto/common-web": "^0.4.7", + "@atproto/crypto": "^0.4.5", + "@atproto/lexicon": "^0.6.0", "@ipld/dag-cbor": "^7.0.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", @@ -491,6 +714,42 @@ "node": ">=18.7.0" } }, + "node_modules/@atproto/repo/node_modules/@atproto/common": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.6.tgz", + "integrity": "sha512-rbWoZwHQNP8jcwjCREVecchw8aaoM5A1NCONyb9PVDWOJLRLCzojYMeIS8IbFqXo6NyIByOGddupADkkLeVBGQ==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.10", + "@atproto/lex-cbor": "0.0.6", + "@atproto/lex-data": "0.0.6", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "pino": "^8.21.0" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/repo/node_modules/@atproto/lexicon": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.0.tgz", + "integrity": "sha512-5veb8aD+J5M0qszLJ+73KSFsFrJBgAY/nM1TSAJvGY7fNc9ZAT+PSUlmIyrdye9YznAZ07yktalls/TwNV7cHQ==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.7", + "@atproto/syntax": "^0.4.2", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/repo/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, "node_modules/@atproto/repo/node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", @@ -535,6 +794,88 @@ "integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==", "license": "MIT" }, + "node_modules/@atproto/tap": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@atproto/tap/-/tap-0.1.1.tgz", + "integrity": "sha512-gW4NzLOxj74TzaDOVzzzt5kl2PdC0r75XkIpYpI5xobwCfsc/DmVtwpuSw1fW9gr4Vzk2Q90S9UE4ifAFl2gyA==", + "license": "MIT", + "dependencies": { + "@atproto/common": "^0.5.6", + "@atproto/lex": "^0.0.9", + "@atproto/syntax": "^0.4.2", + "@atproto/ws-client": "^0.0.4", + "ws": "^8.12.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/tap/node_modules/@atproto/common": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.6.tgz", + "integrity": "sha512-rbWoZwHQNP8jcwjCREVecchw8aaoM5A1NCONyb9PVDWOJLRLCzojYMeIS8IbFqXo6NyIByOGddupADkkLeVBGQ==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.10", + "@atproto/lex-cbor": "0.0.6", + "@atproto/lex-data": "0.0.6", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "pino": "^8.21.0" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/tap/node_modules/@atproto/syntax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz", + "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==", + "license": "MIT" + }, + "node_modules/@atproto/tap/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/@atproto/ws-client": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@atproto/ws-client/-/ws-client-0.0.4.tgz", + "integrity": "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg==", + "license": "MIT", + "dependencies": { + "@atproto/common": "^0.5.3", + "ws": "^8.12.0" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/ws-client/node_modules/@atproto/common": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@atproto/common/-/common-0.5.6.tgz", + "integrity": "sha512-rbWoZwHQNP8jcwjCREVecchw8aaoM5A1NCONyb9PVDWOJLRLCzojYMeIS8IbFqXo6NyIByOGddupADkkLeVBGQ==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.10", + "@atproto/lex-cbor": "0.0.6", + "@atproto/lex-data": "0.0.6", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "pino": "^8.21.0" + }, + "engines": { + "node": ">=18.7.0" + } + }, + "node_modules/@atproto/ws-client/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, "node_modules/@atproto/xrpc": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.5.tgz", @@ -2553,6 +2894,27 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -9108,7 +9470,6 @@ "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "dev": true, "license": "MIT" }, "node_modules/collapse-white-space": { @@ -9217,6 +9578,17 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -11884,7 +12256,8 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/gzip-size": { "version": "6.0.0", @@ -15638,7 +16011,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, "license": "MIT" }, "node_modules/path-exists": { @@ -15920,7 +16292,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -18024,7 +18395,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -18041,7 +18411,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -18059,7 +18428,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -18451,6 +18819,12 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unicode-segmenter": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz", + "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==", + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", diff --git a/package.json b/package.json index 01dba955..13be275d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "TZ=UTC next dev --turbo", "publish-lexicons": "tsx lexicons/publish.ts", "generate-db-types": "supabase gen types --local > supabase/database.types.ts && drizzle-kit introspect && rm -rf ./drizzle/*.sql ./drizzle/meta", - "lexgen": "tsx ./lexicons/build.ts && lex gen-api ./lexicons/api ./lexicons/pub/leaflet/document.json ./lexicons/pub/leaflet/comment.json ./lexicons/pub/leaflet/publication.json ./lexicons/pub/leaflet/*/* ./lexicons/com/atproto/*/* ./lexicons/app/bsky/*/* --yes && tsx ./lexicons/fix-extensions.ts ./lexicons/api", + "lexgen": "tsx ./lexicons/build.ts && lex gen-api ./lexicons/api ./lexicons/pub/leaflet/document.json ./lexicons/pub/leaflet/comment.json ./lexicons/pub/leaflet/publication.json ./lexicons/pub/leaflet/content.json ./lexicons/pub/leaflet/*/* ./lexicons/com/atproto/*/* ./lexicons/app/bsky/*/* ./lexicons/site/*/* ./lexicons/site/*/*/* --yes && tsx ./lexicons/fix-extensions.ts ./lexicons/api", "wrangler-dev": "wrangler dev", "build-appview": "esbuild appview/index.ts --outfile=appview/dist/index.js --bundle --platform=node", "build-feed-service": "esbuild feeds/index.ts --outfile=feeds/dist/index.js --bundle --platform=node", @@ -26,6 +26,7 @@ "@atproto/oauth-client-node": "^0.3.8", "@atproto/sync": "^0.1.34", "@atproto/syntax": "^0.3.3", + "@atproto/tap": "^0.1.1", "@atproto/xrpc": "^0.7.5", "@atproto/xrpc-server": "^0.9.5", "@hono/node-server": "^1.14.3", diff --git a/patterns/lexicons.md b/patterns/lexicons.md new file mode 100644 index 00000000..0332a953 --- /dev/null +++ b/patterns/lexicons.md @@ -0,0 +1,284 @@ +# Lexicon System + +## Overview + +Lexicons define the schema for AT Protocol records. This project has two namespaces: +- **`pub.leaflet.*`** - Leaflet-specific lexicons (documents, publications, blocks, etc.) +- **`site.standard.*`** - Standard site lexicons for interoperability + +The lexicons are defined as TypeScript in `lexicons/src/`, built to JSON in `lexicons/pub/leaflet/` and `lexicons/site/standard/`, and TypeScript types are generated in `lexicons/api/`. + +## Key Files + +- **`lexicons/src/*.ts`** - Source definitions for `pub.leaflet.*` lexicons +- **`lexicons/site/standard/**/*.json`** - JSON definitions for `site.standard.*` lexicons (manually maintained) +- **`lexicons/build.ts`** - Builds TypeScript sources to JSON +- **`lexicons/api/`** - Generated TypeScript types and client +- **`package.json`** - Contains `lexgen` script + +## Running Lexicon Generation + +```bash +npm run lexgen +``` + +This runs: +1. `tsx ./lexicons/build.ts` - Builds `pub.leaflet.*` JSON from TypeScript +2. `lex gen-api` - Generates TypeScript types from all JSON lexicons +3. `tsx ./lexicons/fix-extensions.ts` - Fixes import extensions + +## Adding a New pub.leaflet Lexicon + +### 1. Create the Source Definition + +Create a file in `lexicons/src/` (e.g., `lexicons/src/myLexicon.ts`): + +```typescript +import { LexiconDoc } from "@atproto/lexicon"; + +export const PubLeafletMyLexicon: LexiconDoc = { + lexicon: 1, + id: "pub.leaflet.myLexicon", + defs: { + main: { + type: "record", // or "object" for non-record types + key: "tid", + record: { + type: "object", + required: ["field1"], + properties: { + field1: { type: "string", maxLength: 1000 }, + field2: { type: "integer", minimum: 0 }, + optionalRef: { type: "ref", ref: "other.lexicon#def" }, + }, + }, + }, + // Additional defs for sub-objects + subType: { + type: "object", + properties: { + nested: { type: "string" }, + }, + }, + }, +}; +``` + +### 2. Add to Build + +Update `lexicons/build.ts`: + +```typescript +import { PubLeafletMyLexicon } from "./src/myLexicon"; + +const lexicons = [ + // ... existing lexicons + PubLeafletMyLexicon, +]; +``` + +### 3. Update lexgen Command (if needed) + +If your lexicon is at the top level of `pub/leaflet/` (not in a subdirectory), add it to the `lexgen` script in `package.json`: + +```json +"lexgen": "tsx ./lexicons/build.ts && lex gen-api ./lexicons/api ./lexicons/pub/leaflet/document.json ./lexicons/pub/leaflet/myLexicon.json ./lexicons/pub/leaflet/*/* ..." +``` + +Note: Files in subdirectories (`pub/leaflet/*/*`) are automatically included. + +### 4. Regenerate Types + +```bash +npm run lexgen +``` + +### 5. Use the Generated Types + +```typescript +import { PubLeafletMyLexicon } from "lexicons/api"; + +// Type for the record +type MyRecord = PubLeafletMyLexicon.Record; + +// Validation +const result = PubLeafletMyLexicon.validateRecord(data); +if (result.success) { + // result.value is typed +} + +// Type guard +if (PubLeafletMyLexicon.isRecord(data)) { + // data is typed as Record +} +``` + +## Adding a New site.standard Lexicon + +### 1. Create the JSON Definition + +Create a file in `lexicons/site/standard/` (e.g., `lexicons/site/standard/myType.json`): + +```json +{ + "lexicon": 1, + "id": "site.standard.myType", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["field1"], + "properties": { + "field1": { + "type": "string", + "maxLength": 1000 + } + } + } + } + } +} +``` + +### 2. Regenerate Types + +```bash +npm run lexgen +``` + +The `site/*/* site/*/*/*` globs in the lexgen command automatically pick up new files. + +## Common Lexicon Patterns + +### Referencing Other Lexicons + +```typescript +// Reference another lexicon's main def +{ type: "ref", ref: "pub.leaflet.publication" } + +// Reference a specific def within a lexicon +{ type: "ref", ref: "pub.leaflet.publication#theme" } + +// Reference within the same lexicon +{ type: "ref", ref: "#myDef" } +``` + +### Union Types + +```typescript +{ + type: "union", + refs: [ + "pub.leaflet.pages.linearDocument", + "pub.leaflet.pages.canvas", + ], +} + +// Open union (allows unknown types) +{ + type: "union", + closed: false, // default is true + refs: ["pub.leaflet.content"], +} +``` + +### Blob Types (for images/files) + +```typescript +{ + type: "blob", + accept: ["image/*"], // or specific types like ["image/png", "image/jpeg"] + maxSize: 1000000, // bytes +} +``` + +### Color Types + +The project has color types defined: +- `pub.leaflet.theme.color#rgb` / `#rgba` +- `site.standard.theme.color#rgb` / `#rgba` + +```typescript +// In lexicons/src/theme.ts +export const ColorUnion = { + type: "union", + refs: [ + "pub.leaflet.theme.color#rgba", + "pub.leaflet.theme.color#rgb", + ], +}; +``` + +## Normalization Between Formats + +Use `lexicons/src/normalize.ts` to convert between `pub.leaflet` and `site.standard` formats: + +```typescript +import { + normalizeDocument, + normalizePublication, + isLeafletDocument, + isStandardDocument, + getDocumentPages, +} from "lexicons/src/normalize"; + +// Normalize a document from either format +const normalized = normalizeDocument(record); +if (normalized) { + // normalized is always in site.standard.document format + console.log(normalized.title, normalized.site); + + // Get pages if content is pub.leaflet.content + const pages = getDocumentPages(normalized); +} + +// Normalize a publication +const pub = normalizePublication(record); +if (pub) { + console.log(pub.name, pub.url); +} +``` + +## Handling in Appview (Firehose Consumer) + +When processing records from the firehose in `appview/index.ts`: + +```typescript +import { ids } from "lexicons/api/lexicons"; +import { PubLeafletMyLexicon } from "lexicons/api"; + +// In filterCollections: +filterCollections: [ + ids.PubLeafletMyLexicon, + // ... +], + +// In handleEvent: +if (evt.collection === ids.PubLeafletMyLexicon) { + if (evt.event === "create" || evt.event === "update") { + let record = PubLeafletMyLexicon.validateRecord(evt.record); + if (!record.success) return; + + // Store in database + await supabase.from("my_table").upsert({ + uri: evt.uri.toString(), + data: record.value as Json, + }); + } + if (evt.event === "delete") { + await supabase.from("my_table").delete().eq("uri", evt.uri.toString()); + } +} +``` + +## Publishing Lexicons + +To publish lexicons to an AT Protocol PDS: + +```bash +npm run publish-lexicons +``` + +This runs `lexicons/publish.ts` which publishes lexicons to the configured PDS. diff --git a/supabase/database.types.ts b/supabase/database.types.ts index e9197185..c236d1ab 100644 --- a/supabase/database.types.ts +++ b/supabase/database.types.ts @@ -580,30 +580,30 @@ export type Database = { } leaflets_in_publications: { Row: { - archived: boolean | null cover_image: string | null description: string doc: string | null leaflet: string publication: string + tags: string[] | null title: string } Insert: { - archived?: boolean | null cover_image?: string | null description?: string doc?: string | null leaflet: string publication: string + tags?: string[] | null title?: string } Update: { - archived?: boolean | null cover_image?: string | null description?: string doc?: string | null leaflet?: string publication?: string + tags?: string[] | null title?: string } Relationships: [ @@ -637,6 +637,7 @@ export type Database = { description: string document: string leaflet: string + tags: string[] | null title: string } Insert: { @@ -645,6 +646,7 @@ export type Database = { description?: string document: string leaflet: string + tags?: string[] | null title?: string } Update: { @@ -653,6 +655,7 @@ export type Database = { description?: string document?: string leaflet?: string + tags?: string[] | null title?: string } Relationships: [ @@ -736,19 +739,16 @@ export type Database = { } permission_token_on_homepage: { Row: { - archived: boolean | null created_at: string identity: string token: string } Insert: { - archived?: boolean | null created_at?: string identity: string token: string } Update: { - archived?: boolean | null created_at?: string identity?: string token?: string @@ -762,7 +762,7 @@ export type Database = { referencedColumns: ["id"] }, { - foreignKeyName: "permission_token_creator_token_fkey" + foreignKeyName: "permission_token_on_homepage_token_fkey" columns: ["token"] isOneToOne: false referencedRelation: "permission_tokens" @@ -1080,6 +1080,136 @@ export type Database = { } Relationships: [] } + site_standard_documents: { + Row: { + data: Json + identity_did: string + indexed_at: string + uri: string + } + Insert: { + data: Json + identity_did: string + indexed_at?: string + uri: string + } + Update: { + data?: Json + identity_did?: string + indexed_at?: string + uri?: string + } + Relationships: [ + { + foreignKeyName: "site_standard_documents_identity_did_fkey" + columns: ["identity_did"] + isOneToOne: false + referencedRelation: "identities" + referencedColumns: ["atp_did"] + }, + ] + } + site_standard_documents_in_publications: { + Row: { + document: string + indexed_at: string + publication: string + } + Insert: { + document: string + indexed_at?: string + publication: string + } + Update: { + document?: string + indexed_at?: string + publication?: string + } + Relationships: [ + { + foreignKeyName: "site_standard_documents_in_publications_document_fkey" + columns: ["document"] + isOneToOne: false + referencedRelation: "site_standard_documents" + referencedColumns: ["uri"] + }, + { + foreignKeyName: "site_standard_documents_in_publications_publication_fkey" + columns: ["publication"] + isOneToOne: false + referencedRelation: "site_standard_publications" + referencedColumns: ["uri"] + }, + ] + } + site_standard_publications: { + Row: { + data: Json + identity_did: string + indexed_at: string + uri: string + } + Insert: { + data: Json + identity_did: string + indexed_at?: string + uri: string + } + Update: { + data?: Json + identity_did?: string + indexed_at?: string + uri?: string + } + Relationships: [ + { + foreignKeyName: "site_standard_publications_identity_did_fkey" + columns: ["identity_did"] + isOneToOne: false + referencedRelation: "identities" + referencedColumns: ["atp_did"] + }, + ] + } + site_standard_subscriptions: { + Row: { + created_at: string + identity: string + publication: string + record: Json + uri: string + } + Insert: { + created_at?: string + identity: string + publication: string + record: Json + uri: string + } + Update: { + created_at?: string + identity?: string + publication?: string + record?: Json + uri?: string + } + Relationships: [ + { + foreignKeyName: "site_standard_subscriptions_identity_fkey" + columns: ["identity"] + isOneToOne: false + referencedRelation: "identities" + referencedColumns: ["atp_did"] + }, + { + foreignKeyName: "site_standard_subscriptions_publication_fkey" + columns: ["publication"] + isOneToOne: false + referencedRelation: "site_standard_publications" + referencedColumns: ["uri"] + }, + ] + } subscribers_to_publications: { Row: { created_at: string -- 2.51.2