···11+{
22+ "lexicon": 1,
33+ "id": "com.atproto.label.defs",
44+ "defs": {
55+ "label": {
66+ "type": "object",
77+ "description": "Metadata tag on an atproto resource (eg, repo or record).",
88+ "required": ["src", "uri", "val", "cts"],
99+ "properties": {
1010+ "ver": {
1111+ "type": "integer",
1212+ "description": "The AT Protocol version of the label object."
1313+ },
1414+ "src": {
1515+ "type": "string",
1616+ "format": "did",
1717+ "description": "DID of the actor who created this label."
1818+ },
1919+ "uri": {
2020+ "type": "string",
2121+ "format": "uri",
2222+ "description": "AT URI of the record, repository (account), or other resource that this label applies to."
2323+ },
2424+ "cid": {
2525+ "type": "string",
2626+ "format": "cid",
2727+ "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
2828+ },
2929+ "val": {
3030+ "type": "string",
3131+ "maxLength": 128,
3232+ "description": "The short string name of the value or type of this label."
3333+ },
3434+ "neg": {
3535+ "type": "boolean",
3636+ "description": "If true, this is a negation label, overwriting a previous label."
3737+ },
3838+ "cts": {
3939+ "type": "string",
4040+ "format": "datetime",
4141+ "description": "Timestamp when this label was created."
4242+ },
4343+ "exp": {
4444+ "type": "string",
4545+ "format": "datetime",
4646+ "description": "Timestamp at which this label expires (no longer applies)."
4747+ },
4848+ "sig": {
4949+ "type": "bytes",
5050+ "description": "Signature of dag-cbor encoded label."
5151+ }
5252+ }
5353+ },
5454+ "selfLabels": {
5555+ "type": "object",
5656+ "description": "Metadata tags on an atproto record, published by the author within the record.",
5757+ "required": ["values"],
5858+ "properties": {
5959+ "values": {
6060+ "type": "array",
6161+ "items": { "type": "ref", "ref": "#selfLabel" },
6262+ "maxLength": 10
6363+ }
6464+ }
6565+ },
6666+ "selfLabel": {
6767+ "type": "object",
6868+ "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
6969+ "required": ["val"],
7070+ "properties": {
7171+ "val": {
7272+ "type": "string",
7373+ "maxLength": 128,
7474+ "description": "The short string name of the value or type of this label."
7575+ }
7676+ }
7777+ },
7878+ "labelValueDefinition": {
7979+ "type": "object",
8080+ "description": "Declares a label value and its expected interpretations and behaviors.",
8181+ "required": ["identifier", "severity", "blurs", "locales"],
8282+ "properties": {
8383+ "identifier": {
8484+ "type": "string",
8585+ "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
8686+ "maxLength": 100,
8787+ "maxGraphemes": 100
8888+ },
8989+ "severity": {
9090+ "type": "string",
9191+ "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
9292+ "knownValues": ["inform", "alert", "none"]
9393+ },
9494+ "blurs": {
9595+ "type": "string",
9696+ "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
9797+ "knownValues": ["content", "media", "none"]
9898+ },
9999+ "defaultSetting": {
100100+ "type": "string",
101101+ "description": "The default setting for this label.",
102102+ "knownValues": ["ignore", "warn", "hide"],
103103+ "default": "warn"
104104+ },
105105+ "adultOnly": {
106106+ "type": "boolean",
107107+ "description": "Does the user need to have adult content enabled in order to configure this label?"
108108+ },
109109+ "locales": {
110110+ "type": "array",
111111+ "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" }
112112+ }
113113+ }
114114+ },
115115+ "labelValueDefinitionStrings": {
116116+ "type": "object",
117117+ "description": "Strings which describe the label in the UI, localized into a specific language.",
118118+ "required": ["lang", "name", "description"],
119119+ "properties": {
120120+ "lang": {
121121+ "type": "string",
122122+ "description": "The code of the language these strings are written in.",
123123+ "format": "language"
124124+ },
125125+ "name": {
126126+ "type": "string",
127127+ "description": "A short human-readable name for the label.",
128128+ "maxGraphemes": 64,
129129+ "maxLength": 640
130130+ },
131131+ "description": {
132132+ "type": "string",
133133+ "description": "A longer description of what the label means and why it might be applied.",
134134+ "maxGraphemes": 10000,
135135+ "maxLength": 100000
136136+ }
137137+ }
138138+ },
139139+ "labelValue": {
140140+ "type": "string",
141141+ "knownValues": [
142142+ "!hide",
143143+ "!no-promote",
144144+ "!warn",
145145+ "!no-unauthenticated",
146146+ "dmca-violation",
147147+ "doxxing",
148148+ "porn",
149149+ "sexual",
150150+ "nudity",
151151+ "nsfl",
152152+ "gore"
153153+ ]
154154+ }
155155+ }
156156+}
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import {
55+ type Auth,
66+ type Options as XrpcOptions,
77+ Server as XrpcServer,
88+ type StreamConfigOrHandler,
99+ type MethodConfigOrHandler,
1010+ createServer as createXrpcServer,
1111+} from '@atproto/xrpc-server'
1212+import { schemas } from './lexicons.js'
1313+1414+export function createServer(options?: XrpcOptions): Server {
1515+ return new Server(options)
1616+}
1717+1818+export class Server {
1919+ xrpc: XrpcServer
2020+ org: OrgNS
2121+ com: ComNS
2222+2323+ constructor(options?: XrpcOptions) {
2424+ this.xrpc = createXrpcServer(schemas, options)
2525+ this.org = new OrgNS(this)
2626+ this.com = new ComNS(this)
2727+ }
2828+}
2929+3030+export class OrgNS {
3131+ _server: Server
3232+ atmosphereconf: OrgAtmosphereconfNS
3333+3434+ constructor(server: Server) {
3535+ this._server = server
3636+ this.atmosphereconf = new OrgAtmosphereconfNS(server)
3737+ }
3838+}
3939+4040+export class OrgAtmosphereconfNS {
4141+ _server: Server
4242+4343+ constructor(server: Server) {
4444+ this._server = server
4545+ }
4646+}
4747+4848+export class ComNS {
4949+ _server: Server
5050+ atproto: ComAtprotoNS
5151+5252+ constructor(server: Server) {
5353+ this._server = server
5454+ this.atproto = new ComAtprotoNS(server)
5555+ }
5656+}
5757+5858+export class ComAtprotoNS {
5959+ _server: Server
6060+ repo: ComAtprotoRepoNS
6161+6262+ constructor(server: Server) {
6363+ this._server = server
6464+ this.repo = new ComAtprotoRepoNS(server)
6565+ }
6666+}
6767+6868+export class ComAtprotoRepoNS {
6969+ _server: Server
7070+7171+ constructor(server: Server) {
7272+ this._server = server
7373+ }
7474+}
+298
src/lexicon/lexicons.ts
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import {
55+ type LexiconDoc,
66+ Lexicons,
77+ ValidationError,
88+ type ValidationResult,
99+} from '@atproto/lexicon'
1010+import { type $Typed, is$typed, maybe$typed } from './util.js'
1111+1212+export const schemaDict = {
1313+ ComAtprotoLabelDefs: {
1414+ lexicon: 1,
1515+ id: 'com.atproto.label.defs',
1616+ defs: {
1717+ label: {
1818+ type: 'object',
1919+ description:
2020+ 'Metadata tag on an atproto resource (eg, repo or record).',
2121+ required: ['src', 'uri', 'val', 'cts'],
2222+ properties: {
2323+ ver: {
2424+ type: 'integer',
2525+ description: 'The AT Protocol version of the label object.',
2626+ },
2727+ src: {
2828+ type: 'string',
2929+ format: 'did',
3030+ description: 'DID of the actor who created this label.',
3131+ },
3232+ uri: {
3333+ type: 'string',
3434+ format: 'uri',
3535+ description:
3636+ 'AT URI of the record, repository (account), or other resource that this label applies to.',
3737+ },
3838+ cid: {
3939+ type: 'string',
4040+ format: 'cid',
4141+ description:
4242+ "Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
4343+ },
4444+ val: {
4545+ type: 'string',
4646+ maxLength: 128,
4747+ description:
4848+ 'The short string name of the value or type of this label.',
4949+ },
5050+ neg: {
5151+ type: 'boolean',
5252+ description:
5353+ 'If true, this is a negation label, overwriting a previous label.',
5454+ },
5555+ cts: {
5656+ type: 'string',
5757+ format: 'datetime',
5858+ description: 'Timestamp when this label was created.',
5959+ },
6060+ exp: {
6161+ type: 'string',
6262+ format: 'datetime',
6363+ description:
6464+ 'Timestamp at which this label expires (no longer applies).',
6565+ },
6666+ sig: {
6767+ type: 'bytes',
6868+ description: 'Signature of dag-cbor encoded label.',
6969+ },
7070+ },
7171+ },
7272+ selfLabels: {
7373+ type: 'object',
7474+ description:
7575+ 'Metadata tags on an atproto record, published by the author within the record.',
7676+ required: ['values'],
7777+ properties: {
7878+ values: {
7979+ type: 'array',
8080+ items: {
8181+ type: 'ref',
8282+ ref: 'lex:com.atproto.label.defs#selfLabel',
8383+ },
8484+ maxLength: 10,
8585+ },
8686+ },
8787+ },
8888+ selfLabel: {
8989+ type: 'object',
9090+ description:
9191+ 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.',
9292+ required: ['val'],
9393+ properties: {
9494+ val: {
9595+ type: 'string',
9696+ maxLength: 128,
9797+ description:
9898+ 'The short string name of the value or type of this label.',
9999+ },
100100+ },
101101+ },
102102+ labelValueDefinition: {
103103+ type: 'object',
104104+ description:
105105+ 'Declares a label value and its expected interpretations and behaviors.',
106106+ required: ['identifier', 'severity', 'blurs', 'locales'],
107107+ properties: {
108108+ identifier: {
109109+ type: 'string',
110110+ description:
111111+ "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
112112+ maxLength: 100,
113113+ maxGraphemes: 100,
114114+ },
115115+ severity: {
116116+ type: 'string',
117117+ description:
118118+ "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
119119+ knownValues: ['inform', 'alert', 'none'],
120120+ },
121121+ blurs: {
122122+ type: 'string',
123123+ description:
124124+ "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
125125+ knownValues: ['content', 'media', 'none'],
126126+ },
127127+ defaultSetting: {
128128+ type: 'string',
129129+ description: 'The default setting for this label.',
130130+ knownValues: ['ignore', 'warn', 'hide'],
131131+ default: 'warn',
132132+ },
133133+ adultOnly: {
134134+ type: 'boolean',
135135+ description:
136136+ 'Does the user need to have adult content enabled in order to configure this label?',
137137+ },
138138+ locales: {
139139+ type: 'array',
140140+ items: {
141141+ type: 'ref',
142142+ ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings',
143143+ },
144144+ },
145145+ },
146146+ },
147147+ labelValueDefinitionStrings: {
148148+ type: 'object',
149149+ description:
150150+ 'Strings which describe the label in the UI, localized into a specific language.',
151151+ required: ['lang', 'name', 'description'],
152152+ properties: {
153153+ lang: {
154154+ type: 'string',
155155+ description:
156156+ 'The code of the language these strings are written in.',
157157+ format: 'language',
158158+ },
159159+ name: {
160160+ type: 'string',
161161+ description: 'A short human-readable name for the label.',
162162+ maxGraphemes: 64,
163163+ maxLength: 640,
164164+ },
165165+ description: {
166166+ type: 'string',
167167+ description:
168168+ 'A longer description of what the label means and why it might be applied.',
169169+ maxGraphemes: 10000,
170170+ maxLength: 100000,
171171+ },
172172+ },
173173+ },
174174+ labelValue: {
175175+ type: 'string',
176176+ knownValues: [
177177+ '!hide',
178178+ '!no-promote',
179179+ '!warn',
180180+ '!no-unauthenticated',
181181+ 'dmca-violation',
182182+ 'doxxing',
183183+ 'porn',
184184+ 'sexual',
185185+ 'nudity',
186186+ 'nsfl',
187187+ 'gore',
188188+ ],
189189+ },
190190+ },
191191+ },
192192+ OrgAtmosphereconfProfile: {
193193+ lexicon: 1,
194194+ id: 'org.atmosphereconf.profile',
195195+ defs: {
196196+ main: {
197197+ type: 'record',
198198+ description: 'A conference attendee profile.',
199199+ key: 'literal:self',
200200+ record: {
201201+ type: 'object',
202202+ properties: {
203203+ displayName: {
204204+ type: 'string',
205205+ maxGraphemes: 64,
206206+ maxLength: 640,
207207+ },
208208+ description: {
209209+ type: 'string',
210210+ description: 'Free-form profile description text.',
211211+ maxGraphemes: 256,
212212+ maxLength: 2560,
213213+ },
214214+ avatar: {
215215+ type: 'blob',
216216+ description: 'Profile picture for conference attendee',
217217+ accept: ['image/png', 'image/jpeg'],
218218+ maxSize: 1000000,
219219+ },
220220+ banner: {
221221+ type: 'blob',
222222+ description:
223223+ 'Larger horizontal image to display behind profile view.',
224224+ accept: ['image/png', 'image/jpeg'],
225225+ maxSize: 1000000,
226226+ },
227227+ labels: {
228228+ type: 'union',
229229+ description: 'Self-label values for the conference profile.',
230230+ refs: ['lex:com.atproto.label.defs#selfLabels'],
231231+ },
232232+ createdAt: {
233233+ type: 'string',
234234+ format: 'datetime',
235235+ },
236236+ },
237237+ },
238238+ },
239239+ },
240240+ },
241241+ ComAtprotoRepoStrongRef: {
242242+ lexicon: 1,
243243+ id: 'com.atproto.repo.strongRef',
244244+ description: 'A URI with a content-hash fingerprint.',
245245+ defs: {
246246+ main: {
247247+ type: 'object',
248248+ required: ['uri', 'cid'],
249249+ properties: {
250250+ uri: {
251251+ type: 'string',
252252+ format: 'at-uri',
253253+ },
254254+ cid: {
255255+ type: 'string',
256256+ format: 'cid',
257257+ },
258258+ },
259259+ },
260260+ },
261261+ },
262262+} as const satisfies Record<string, LexiconDoc>
263263+export const schemas = Object.values(schemaDict) satisfies LexiconDoc[]
264264+export const lexicons: Lexicons = new Lexicons(schemas)
265265+266266+export function validate<T extends { $type: string }>(
267267+ v: unknown,
268268+ id: string,
269269+ hash: string,
270270+ requiredType: true,
271271+): ValidationResult<T>
272272+export function validate<T extends { $type?: string }>(
273273+ v: unknown,
274274+ id: string,
275275+ hash: string,
276276+ requiredType?: false,
277277+): ValidationResult<T>
278278+export function validate(
279279+ v: unknown,
280280+ id: string,
281281+ hash: string,
282282+ requiredType?: boolean,
283283+): ValidationResult {
284284+ return (requiredType ? is$typed : maybe$typed)(v, id, hash)
285285+ ? lexicons.validate(`${id}#${hash}`, v)
286286+ : {
287287+ success: false,
288288+ error: new ValidationError(
289289+ `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
290290+ ),
291291+ }
292292+}
293293+294294+export const ids = {
295295+ ComAtprotoLabelDefs: 'com.atproto.label.defs',
296296+ OrgAtmosphereconfProfile: 'org.atmosphereconf.profile',
297297+ ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
298298+} as const
+146
src/lexicon/types/com/atproto/label/defs.ts
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import { type ValidationResult, BlobRef } from '@atproto/lexicon'
55+import { CID } from 'multiformats/cid'
66+import { validate as _validate } from '../../../../lexicons'
77+import {
88+ type $Typed,
99+ is$typed as _is$typed,
1010+ type OmitKey,
1111+} from '../../../../util'
1212+1313+const is$typed = _is$typed,
1414+ validate = _validate
1515+const id = 'com.atproto.label.defs'
1616+1717+/** Metadata tag on an atproto resource (eg, repo or record). */
1818+export interface Label {
1919+ $type?: 'com.atproto.label.defs#label'
2020+ /** The AT Protocol version of the label object. */
2121+ ver?: number
2222+ /** DID of the actor who created this label. */
2323+ src: string
2424+ /** AT URI of the record, repository (account), or other resource that this label applies to. */
2525+ uri: string
2626+ /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
2727+ cid?: string
2828+ /** The short string name of the value or type of this label. */
2929+ val: string
3030+ /** If true, this is a negation label, overwriting a previous label. */
3131+ neg?: boolean
3232+ /** Timestamp when this label was created. */
3333+ cts: string
3434+ /** Timestamp at which this label expires (no longer applies). */
3535+ exp?: string
3636+ /** Signature of dag-cbor encoded label. */
3737+ sig?: Uint8Array
3838+}
3939+4040+const hashLabel = 'label'
4141+4242+export function isLabel<V>(v: V) {
4343+ return is$typed(v, id, hashLabel)
4444+}
4545+4646+export function validateLabel<V>(v: V) {
4747+ return validate<Label & V>(v, id, hashLabel)
4848+}
4949+5050+/** Metadata tags on an atproto record, published by the author within the record. */
5151+export interface SelfLabels {
5252+ $type?: 'com.atproto.label.defs#selfLabels'
5353+ values: SelfLabel[]
5454+}
5555+5656+const hashSelfLabels = 'selfLabels'
5757+5858+export function isSelfLabels<V>(v: V) {
5959+ return is$typed(v, id, hashSelfLabels)
6060+}
6161+6262+export function validateSelfLabels<V>(v: V) {
6363+ return validate<SelfLabels & V>(v, id, hashSelfLabels)
6464+}
6565+6666+/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
6767+export interface SelfLabel {
6868+ $type?: 'com.atproto.label.defs#selfLabel'
6969+ /** The short string name of the value or type of this label. */
7070+ val: string
7171+}
7272+7373+const hashSelfLabel = 'selfLabel'
7474+7575+export function isSelfLabel<V>(v: V) {
7676+ return is$typed(v, id, hashSelfLabel)
7777+}
7878+7979+export function validateSelfLabel<V>(v: V) {
8080+ return validate<SelfLabel & V>(v, id, hashSelfLabel)
8181+}
8282+8383+/** Declares a label value and its expected interpretations and behaviors. */
8484+export interface LabelValueDefinition {
8585+ $type?: 'com.atproto.label.defs#labelValueDefinition'
8686+ /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
8787+ identifier: string
8888+ /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
8989+ severity: 'inform' | 'alert' | 'none' | (string & {})
9090+ /** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
9191+ blurs: 'content' | 'media' | 'none' | (string & {})
9292+ /** The default setting for this label. */
9393+ defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {})
9494+ /** Does the user need to have adult content enabled in order to configure this label? */
9595+ adultOnly?: boolean
9696+ locales: LabelValueDefinitionStrings[]
9797+}
9898+9999+const hashLabelValueDefinition = 'labelValueDefinition'
100100+101101+export function isLabelValueDefinition<V>(v: V) {
102102+ return is$typed(v, id, hashLabelValueDefinition)
103103+}
104104+105105+export function validateLabelValueDefinition<V>(v: V) {
106106+ return validate<LabelValueDefinition & V>(v, id, hashLabelValueDefinition)
107107+}
108108+109109+/** Strings which describe the label in the UI, localized into a specific language. */
110110+export interface LabelValueDefinitionStrings {
111111+ $type?: 'com.atproto.label.defs#labelValueDefinitionStrings'
112112+ /** The code of the language these strings are written in. */
113113+ lang: string
114114+ /** A short human-readable name for the label. */
115115+ name: string
116116+ /** A longer description of what the label means and why it might be applied. */
117117+ description: string
118118+}
119119+120120+const hashLabelValueDefinitionStrings = 'labelValueDefinitionStrings'
121121+122122+export function isLabelValueDefinitionStrings<V>(v: V) {
123123+ return is$typed(v, id, hashLabelValueDefinitionStrings)
124124+}
125125+126126+export function validateLabelValueDefinitionStrings<V>(v: V) {
127127+ return validate<LabelValueDefinitionStrings & V>(
128128+ v,
129129+ id,
130130+ hashLabelValueDefinitionStrings,
131131+ )
132132+}
133133+134134+export type LabelValue =
135135+ | '!hide'
136136+ | '!no-promote'
137137+ | '!warn'
138138+ | '!no-unauthenticated'
139139+ | 'dmca-violation'
140140+ | 'doxxing'
141141+ | 'porn'
142142+ | 'sexual'
143143+ | 'nudity'
144144+ | 'nsfl'
145145+ | 'gore'
146146+ | (string & {})
+31
src/lexicon/types/com/atproto/repo/strongRef.ts
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import { type ValidationResult, BlobRef } from '@atproto/lexicon'
55+import { CID } from 'multiformats/cid'
66+import { validate as _validate } from '../../../../lexicons'
77+import {
88+ type $Typed,
99+ is$typed as _is$typed,
1010+ type OmitKey,
1111+} from '../../../../util'
1212+1313+const is$typed = _is$typed,
1414+ validate = _validate
1515+const id = 'com.atproto.repo.strongRef'
1616+1717+export interface Main {
1818+ $type?: 'com.atproto.repo.strongRef'
1919+ uri: string
2020+ cid: string
2121+}
2222+2323+const hashMain = 'main'
2424+2525+export function isMain<V>(v: V) {
2626+ return is$typed(v, id, hashMain)
2727+}
2828+2929+export function validateMain<V>(v: V) {
3030+ return validate<Main & V>(v, id, hashMain)
3131+}
+42
src/lexicon/types/org/atmosphereconf/profile.ts
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+import { type ValidationResult, BlobRef } from '@atproto/lexicon'
55+import { CID } from 'multiformats/cid'
66+import { validate as _validate } from '../../../lexicons'
77+import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
88+import type * as ComAtprotoLabelDefs from '../../com/atproto/label/defs.js'
99+1010+const is$typed = _is$typed,
1111+ validate = _validate
1212+const id = 'org.atmosphereconf.profile'
1313+1414+export interface Main {
1515+ $type: 'org.atmosphereconf.profile'
1616+ displayName?: string
1717+ /** Free-form profile description text. */
1818+ description?: string
1919+ /** Profile picture for conference attendee */
2020+ avatar?: BlobRef
2121+ /** Larger horizontal image to display behind profile view. */
2222+ banner?: BlobRef
2323+ labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }
2424+ createdAt?: string
2525+ [k: string]: unknown
2626+}
2727+2828+const hashMain = 'main'
2929+3030+export function isMain<V>(v: V) {
3131+ return is$typed(v, id, hashMain)
3232+}
3333+3434+export function validateMain<V>(v: V) {
3535+ return validate<Main & V>(v, id, hashMain, true)
3636+}
3737+3838+export {
3939+ type Main as Record,
4040+ isMain as isRecord,
4141+ validateMain as validateRecord,
4242+}
+82
src/lexicon/util.ts
···11+/**
22+ * GENERATED CODE - DO NOT MODIFY
33+ */
44+55+import { type ValidationResult } from '@atproto/lexicon'
66+77+export type OmitKey<T, K extends keyof T> = {
88+ [K2 in keyof T as K2 extends K ? never : K2]: T[K2]
99+}
1010+1111+export type $Typed<V, T extends string = string> = V & { $type: T }
1212+export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>
1313+1414+export type $Type<Id extends string, Hash extends string> = Hash extends 'main'
1515+ ? Id
1616+ : `${Id}#${Hash}`
1717+1818+function isObject<V>(v: V): v is V & object {
1919+ return v != null && typeof v === 'object'
2020+}
2121+2222+function is$type<Id extends string, Hash extends string>(
2323+ $type: unknown,
2424+ id: Id,
2525+ hash: Hash,
2626+): $type is $Type<Id, Hash> {
2727+ return hash === 'main'
2828+ ? $type === id
2929+ : // $type === `${id}#${hash}`
3030+ typeof $type === 'string' &&
3131+ $type.length === id.length + 1 + hash.length &&
3232+ $type.charCodeAt(id.length) === 35 /* '#' */ &&
3333+ $type.startsWith(id) &&
3434+ $type.endsWith(hash)
3535+}
3636+3737+export type $TypedObject<
3838+ V,
3939+ Id extends string,
4040+ Hash extends string,
4141+> = V extends {
4242+ $type: $Type<Id, Hash>
4343+}
4444+ ? V
4545+ : V extends { $type?: string }
4646+ ? V extends { $type?: infer T extends $Type<Id, Hash> }
4747+ ? V & { $type: T }
4848+ : never
4949+ : V & { $type: $Type<Id, Hash> }
5050+5151+export function is$typed<V, Id extends string, Hash extends string>(
5252+ v: V,
5353+ id: Id,
5454+ hash: Hash,
5555+): v is $TypedObject<V, Id, Hash> {
5656+ return isObject(v) && '$type' in v && is$type(v.$type, id, hash)
5757+}
5858+5959+export function maybe$typed<V, Id extends string, Hash extends string>(
6060+ v: V,
6161+ id: Id,
6262+ hash: Hash,
6363+): v is V & object & { $type?: $Type<Id, Hash> } {
6464+ return (
6565+ isObject(v) &&
6666+ ('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true)
6767+ )
6868+}
6969+7070+export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>
7171+export type ValidatorParam<V extends Validator> =
7272+ V extends Validator<infer R> ? R : never
7373+7474+/**
7575+ * Utility function that allows to convert a "validate*" utility function into a
7676+ * type predicate.
7777+ */
7878+export function asPredicate<V extends Validator>(validate: V) {
7979+ return function <T>(v: T): v is T & ValidatorParam<V> {
8080+ return validate(v).success
8181+ }
8282+}
+104
src/pages/api/profile/create.ts
···11+import type { APIRoute } from 'astro'
22+import { getOAuthClient } from '../../../lib/context'
33+import { getSession } from '../../../lib/session'
44+import { Agent, BlobRef } from '@atproto/api'
55+import type { Main as ProfileRecord } from '../../../lexicon/types/org/atmosphereconf/profile'
66+77+async function fileToBlob(agent: Agent, file: File): Promise<BlobRef> {
88+ const arrayBuffer = await file.arrayBuffer()
99+ const uint8Array = new Uint8Array(arrayBuffer)
1010+1111+ const response = await agent.com.atproto.repo.uploadBlob(uint8Array, {
1212+ encoding: file.type,
1313+ })
1414+1515+ return response.data.blob
1616+}
1717+1818+export const POST: APIRoute = async ({ request, cookies, redirect }) => {
1919+ try {
2020+ const session = getSession(cookies)
2121+ const oauthClient = getOAuthClient(cookies)
2222+2323+ if (!session.did) {
2424+ return new Response('Unauthorized', { status: 401 })
2525+ }
2626+2727+ const oauthSession = await oauthClient.restore(session.did)
2828+ if (!oauthSession) {
2929+ return new Response('Session expired', { status: 401 })
3030+ }
3131+3232+ const agent = new Agent(oauthSession)
3333+ const formData = await request.formData()
3434+3535+ // Extract form data
3636+ const displayName = formData.get('displayName')
3737+ const description = formData.get('description')
3838+ const avatarFile = formData.get('avatar')
3939+ const bannerFile = formData.get('banner')
4040+4141+ if (!displayName || typeof displayName !== 'string') {
4242+ return new Response('Display name is required', { status: 400 })
4343+ }
4444+4545+ // Validate file sizes
4646+ if (avatarFile instanceof File && avatarFile.size > 0 && avatarFile.size > 1000000) {
4747+ return new Response('Avatar file size must be less than 1MB', { status: 400 })
4848+ }
4949+5050+ if (bannerFile instanceof File && bannerFile.size > 0 && bannerFile.size > 1000000) {
5151+ return new Response('Banner file size must be less than 1MB', { status: 400 })
5252+ }
5353+5454+ // Build the profile record
5555+ const record: Omit<ProfileRecord, '$type'> = {
5656+ displayName: displayName.slice(0, 64),
5757+ description: typeof description === 'string' ? description.slice(0, 256) : undefined,
5858+ createdAt: new Date().toISOString(),
5959+ }
6060+6161+ // Upload avatar if provided
6262+ if (avatarFile instanceof File && avatarFile.size > 0) {
6363+ try {
6464+ record.avatar = await fileToBlob(agent, avatarFile)
6565+ } catch (err) {
6666+ console.error('Failed to upload avatar:', err)
6767+ return new Response('Failed to upload avatar', { status: 500 })
6868+ }
6969+ }
7070+7171+ // Upload banner if provided
7272+ if (bannerFile instanceof File && bannerFile.size > 0) {
7373+ try {
7474+ record.banner = await fileToBlob(agent, bannerFile)
7575+ } catch (err) {
7676+ console.error('Failed to upload banner:', err)
7777+ return new Response('Failed to upload banner', { status: 500 })
7878+ }
7979+ }
8080+8181+ // Create or update the profile record
8282+ try {
8383+ await agent.com.atproto.repo.putRecord({
8484+ repo: agent.assertDid,
8585+ collection: 'org.atmosphereconf.profile',
8686+ rkey: 'self',
8787+ record: {
8888+ $type: 'org.atmosphereconf.profile',
8989+ ...record,
9090+ },
9191+ })
9292+9393+ return redirect('/')
9494+ } catch (err) {
9595+ console.error('Failed to create profile:', err)
9696+ const error = err instanceof Error ? err.message : 'unexpected error'
9797+ return new Response(`Failed to create profile: ${error}`, { status: 500 })
9898+ }
9999+ } catch (err) {
100100+ console.error('Profile creation failed:', err)
101101+ const error = err instanceof Error ? err.message : 'unexpected error'
102102+ return new Response(`Profile creation failed: ${error}`, { status: 500 })
103103+ }
104104+}