Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
87
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial XRPC work

+2072 -139
+47
README.md
··· 55 55 LOCAL_DEV=true 56 56 ``` 57 57 58 + ### Local Domain XRPC (ServiceAuth via PDS Proxy) 59 + 60 + `apps/main-app` exposes domain claim/status XRPC endpoints: 61 + 62 + - `place.wisp.v2.domain.claim` (procedure / POST) 63 + - `place.wisp.v2.domain.getStatus` (query / GET) 64 + 65 + The server validates **serviceAuth JWTs** (not cookie auth, not direct end-user access JWTs) on `/xrpc/*`. 66 + 67 + Set these env vars in your active `.env`: 68 + 69 + ```env 70 + LOCAL_DEV=true 71 + SERVICE_DID=did:web:regentsmacbookair 72 + SERVICE_IDS="#wisp_xrpc" 73 + SERVICE_ENDPOINT=https://regentsmacbookair 74 + ``` 75 + 76 + Notes: 77 + 78 + - `/.well-known/atproto-did` returns `SERVICE_DID`. 79 + - `/.well-known/did.json` publishes the DID doc, including `service` entries from `SERVICE_IDS`. 80 + - `SERVICE_IDS` must be quoted when it starts with `#` (otherwise dotenv treats it as a comment). 81 + - Service identity keys are stored in `service_identity_keys` in Postgres. 82 + If `SERVICE_PUBLIC_KEY_MULTIBASE` and `SERVICE_PRIVATE_KEY_MULTIBASE` are not set, a keypair is generated once and persisted. 83 + 84 + ### Local TLS Requirement (No Auto Cert Generation) 85 + 86 + Some PDS proxy flows require HTTPS on `:443` for the proxied service endpoint. 87 + Cert generation is intentionally manual so SANs are explicit and correct for your environment. 88 + 89 + Example with `mkcert`: 90 + 91 + ```bash 92 + mkcert -cert-file certs/dev-cert.pem -key-file certs/dev-key.pem regentsmacbookair localhost 100.64.0.2 93 + ``` 94 + 95 + Use SANs that match exactly what your PDS will call (hostname and/or IP). 96 + `apps/main-app` can terminate TLS directly in local dev with: 97 + 98 + ```env 99 + PORT=443 100 + LOCAL_DEV_TLS=true 101 + LOCAL_TLS_CERT_PATH=./certs/dev-cert.pem 102 + LOCAL_TLS_KEY_PATH=./certs/dev-key.pem 103 + ``` 104 + 58 105 59 106 ```bash 60 107 # Hosting service
+6 -1
apps/main-app/package.json
··· 12 12 "screenshot": "bun run scripts/screenshot-sites.ts" 13 13 }, 14 14 "dependencies": { 15 + "@atcute/crypto": "^2.3.0", 16 + "@atcute/identity": "^1.1.3", 17 + "@atcute/identity-resolver": "^1.2.2", 18 + "@atcute/oauth-crypto": "^0.1.0", 19 + "@atcute/xrpc-server": "^0.1.10", 15 20 "@atproto-labs/did-resolver": "^0.2.4", 16 21 "@atproto/api": "^0.17.7", 17 22 "@atproto/common-web": "^0.4.6", ··· 32 37 "@radix-ui/react-tabs": "^1.1.13", 33 38 "@tanstack/react-query": "^5.90.2", 34 39 "@wispplace/atproto-utils": "workspace:*", 35 - "@wispplace/css": "workspace:*", 36 40 "@wispplace/constants": "workspace:*", 41 + "@wispplace/css": "workspace:*", 37 42 "@wispplace/database": "workspace:*", 38 43 "@wispplace/fs-utils": "workspace:*", 39 44 "@wispplace/lexicons": "workspace:*",
+123 -12
apps/main-app/src/index.ts
··· 1 1 // Fix for Elysia issue with Bun, (see https://github.com/oven-sh/bun/issues/12161) 2 2 process.getBuiltinModule = require; 3 3 4 + import { existsSync } from 'node:fs' 5 + 4 6 import { Elysia, t } from 'elysia' 5 7 import type { Context } from 'elysia' 6 8 import { cors } from '@elysiajs/cors' ··· 16 18 rotateKeysIfNeeded 17 19 } from './lib/oauth-client' 18 20 import { getCookieSecret, closeDatabase } from './lib/db' 21 + import { ensureServiceIdentityKeypair } from './lib/service-identity' 19 22 import { authRoutes } from './routes/auth' 20 23 import { wispRoutes } from './routes/wisp' 21 24 import { domainRoutes } from './routes/domain' 22 25 import { userRoutes } from './routes/user' 23 26 import { siteRoutes } from './routes/site' 27 + import { xrpcRoutes } from './routes/xrpc' 24 28 import { csrfProtection } from './lib/csrf' 25 29 import { DNSVerificationWorker } from './lib/dns-verification-worker' 26 30 import { createLogger, logCollector, initializeGrafanaExporters } from '@wispplace/observability' ··· 35 39 }) 36 40 37 41 const logger = createLogger('main-app') 42 + const isLocalDev = Bun.env.LOCAL_DEV === 'true' 43 + const oauthAuthorizationServer = Bun.env.OAUTH_AUTHORIZATION_SERVER ?? 'https://atproto.wisp.place' 44 + const defaultServiceDid = isLocalDev ? 'did:web:localhost' : 'did:web:wisp.place' 45 + const serviceDid = Bun.env.SERVICE_DID ?? defaultServiceDid 46 + const parsedServiceIds = (Bun.env.SERVICE_IDS ?? '') 47 + .split(',') 48 + .map((id) => id.trim()) 49 + .filter((id) => id.length > 0 && id.startsWith('#')) 50 + const didServiceIds = parsedServiceIds.length > 0 51 + ? Array.from(new Set(parsedServiceIds)) 52 + : ['#wisp_xrpc'] 53 + const serverPort = Number(Bun.env.PORT ?? (isLocalDev ? '443' : '80')) 54 + const localTlsEnabled = isLocalDev && Bun.env.LOCAL_DEV_TLS !== 'false' 55 + const localTlsCertPath = Bun.env.LOCAL_TLS_CERT_PATH ?? './certs/dev-cert.pem' 56 + const localTlsKeyPath = Bun.env.LOCAL_TLS_KEY_PATH ?? './certs/dev-key.pem' 57 + 58 + logger.info('[Server] Local TLS config', { 59 + isLocalDev, 60 + localTlsEnabled, 61 + port: serverPort, 62 + certPath: localTlsEnabled ? localTlsCertPath : undefined, 63 + keyPath: localTlsEnabled ? localTlsKeyPath : undefined 64 + }) 38 65 39 66 const config: Config = { 40 67 domain: (Bun.env.DOMAIN ?? `https://${BASE_HOST}`) as Config['domain'], ··· 46 73 47 74 // Get or generate cookie signing secret 48 75 const cookieSecret = await getCookieSecret() 76 + const serviceIdentity = await ensureServiceIdentityKeypair( 77 + Bun.env.SERVICE_PUBLIC_KEY_MULTIBASE ?? null, 78 + Bun.env.SERVICE_PRIVATE_KEY_MULTIBASE ?? null 79 + ) 80 + const servicePublicKeyMultibase = serviceIdentity.publicKeyMultibase 49 81 50 82 const client = await getOAuthClient(config) 51 83 ··· 169 201 } 170 202 }) 171 203 .use(csrfProtection()) 172 - .get('/', async ({ set }) => { 204 + .get('/', async ({ request, set }) => { 173 205 // Build dynamic login URL for AT Protocol OAuth entryway 174 - const isLocalDev = Bun.env.LOCAL_DEV === 'true' 175 206 const loginUrl = isLocalDev 176 - ? 'http://127.0.0.1:8000/api/auth/login' 207 + ? `${new URL(request.url).origin}/api/auth/login` 177 208 : `${config.domain}/api/auth/login` 178 209 const atprotoLoginUrl = `https://atproto.wisp.place/?next=${encodeURIComponent(loginUrl)}` 179 210 ··· 183 214 return html.replaceAll('{{ATPROTO_LOGIN_URL}}', atprotoLoginUrl) 184 215 }) 185 216 .use(authRoutes(client, cookieSecret)) 217 + .use(xrpcRoutes()) 186 218 .use(wispRoutes(client, cookieSecret)) 187 219 .use(domainRoutes(client, cookieSecret)) 188 220 .use(userRoutes(client, cookieSecret)) ··· 278 310 }) 279 311 return metadata 280 312 }) 313 + .get('/.well-known/oauth-protected-resource', ({ request }) => { 314 + const resource = new URL(request.url).origin 315 + 316 + return { 317 + resource, 318 + authorization_servers: [oauthAuthorizationServer], 319 + bearer_methods_supported: ['header'], 320 + } 321 + }) 322 + .get('/.well-known/did.json', ({ request, set }) => { 323 + set.headers['Content-Type'] = 'application/did+json' 324 + 325 + const serviceEndpoint = Bun.env.SERVICE_ENDPOINT ?? new URL(request.url).origin 326 + const contexts = ['https://www.w3.org/ns/did/v1'] 327 + if (servicePublicKeyMultibase) { 328 + contexts.push('https://w3id.org/security/multikey/v1') 329 + } 330 + 331 + const services = didServiceIds.map((id) => ({ 332 + id, 333 + type: 'AtprotoService', 334 + serviceEndpoint 335 + })) 336 + 337 + const verificationMethod = servicePublicKeyMultibase 338 + ? [ 339 + { 340 + id: `${serviceDid}#atproto`, 341 + type: 'Multikey', 342 + controller: serviceDid, 343 + publicKeyMultibase: servicePublicKeyMultibase 344 + } 345 + ] 346 + : undefined 347 + 348 + return { 349 + '@context': contexts, 350 + id: serviceDid, 351 + verificationMethod, 352 + service: services 353 + } 354 + }) 281 355 .get('/jwks.json', async ({ set }) => { 282 356 // Prevent caching to ensure clients always get fresh keys after rotation 283 357 set.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' ··· 335 409 .get('/.well-known/atproto-did', ({ set }) => { 336 410 // Return plain text DID for AT Protocol domain verification 337 411 set.headers['Content-Type'] = 'text/plain' 338 - return 'did:plc:7puq73yz2hkvbcpdhnsze2qw' 412 + return serviceDid 339 413 }) 340 414 .use(cors({ 341 - origin: config.domain, 415 + origin: isLocalDev 416 + ? [ 417 + config.domain, 418 + /^http:\/\/127\.0\.0\.1:\d+$/, 419 + /^http:\/\/localhost:\d+$/, 420 + /^https:\/\/127\.0\.0\.1:\d+$/, 421 + /^https:\/\/localhost:\d+$/ 422 + ] 423 + : config.domain, 342 424 credentials: true, 343 425 methods: ['GET', 'POST', 'DELETE', 'PUT', 'PATCH', 'OPTIONS'], 344 - allowedHeaders: ['Content-Type', 'Authorization', 'Origin', 'X-Forwarded-Host'], 345 - exposeHeaders: ['Content-Type'], 426 + allowedHeaders: [ 427 + 'Content-Type', 428 + 'Authorization', 429 + 'Origin', 430 + 'X-Forwarded-Host', 431 + 'DPoP', 432 + 'dpop', 433 + 'DPoP-Nonce', 434 + 'dpop-nonce' 435 + ], 436 + exposeHeaders: ['Content-Type', 'DPoP-Nonce', 'dpop-nonce'], 346 437 maxAge: 86400 // 24 hours 347 438 })) 348 - .listen({ 349 - port: 8000, 350 - hostname: '0.0.0.0' 351 - }) 439 + .listen( 440 + localTlsEnabled 441 + ? (() => { 442 + if (!existsSync(localTlsCertPath)) { 443 + throw new Error(`LOCAL_DEV TLS cert not found at ${localTlsCertPath}`) 444 + } 445 + if (!existsSync(localTlsKeyPath)) { 446 + throw new Error(`LOCAL_DEV TLS key not found at ${localTlsKeyPath}`) 447 + } 448 + 449 + return { 450 + port: serverPort, 451 + hostname: '0.0.0.0', 452 + tls: { 453 + cert: Bun.file(localTlsCertPath), 454 + key: Bun.file(localTlsKeyPath) 455 + } 456 + } 457 + })() 458 + : { 459 + port: serverPort, 460 + hostname: '0.0.0.0' 461 + } 462 + ) 352 463 353 464 console.log( 354 - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 465 + `🦊 Elysia is running at ${localTlsEnabled ? 'https' : 'http'}://${app.server?.hostname}:${app.server?.port}` 355 466 ) 356 467 357 468 // Graceful shutdown
+62 -29
apps/main-app/src/lib/db.ts
··· 1 1 import { SQL } from "bun"; 2 - import { BASE_HOST } from "@wispplace/constants"; 2 + import { isValidHandle, toDomain } from "./domain-utils"; 3 + 4 + export { isValidHandle, toDomain } from "./domain-utils"; 3 5 4 6 export const db = new SQL( 5 7 process.env.NODE_ENV === 'production' ··· 43 45 ) 44 46 `; 45 47 48 + // Service identity keys for did.json verificationMethod 49 + await db` 50 + CREATE TABLE IF NOT EXISTS service_identity_keys ( 51 + id TEXT PRIMARY KEY DEFAULT 'default', 52 + public_key_multibase TEXT NOT NULL, 53 + private_key_multibase TEXT, 54 + created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()), 55 + updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) 56 + ) 57 + `; 58 + 46 59 // Domains table maps subdomain -> DID (now supports up to 3 domains per user) 47 60 await db` 48 61 CREATE TABLE IF NOT EXISTS domains ( ··· 74 87 75 88 try { 76 89 await db`ALTER TABLE oauth_states ADD COLUMN IF NOT EXISTS expires_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) + 3600`; 90 + } catch (err) { 91 + // Column might already exist, ignore 92 + } 93 + 94 + try { 95 + await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())`; 96 + } catch (err) { 97 + // Column might already exist, ignore 98 + } 99 + 100 + try { 101 + await db`ALTER TABLE service_identity_keys ADD COLUMN IF NOT EXISTS private_key_multibase TEXT`; 77 102 } catch (err) { 78 103 // Column might already exist, ignore 79 104 } ··· 240 265 } 241 266 }) 242 267 ]); 243 - 244 - const RESERVED_HANDLES = new Set([ 245 - "www", 246 - "api", 247 - "admin", 248 - "static", 249 - "public", 250 - "preview", 251 - "slingshot", 252 - "plc", 253 - "constellation", 254 - "cdn", 255 - "pds", 256 - "staging", 257 - "auth" 258 - ]); 259 - 260 - export const isValidHandle = (handle: string): boolean => { 261 - const h = handle.trim().toLowerCase(); 262 - if (h.length < 3 || h.length > 63) return false; 263 - if (!/^[a-z0-9-]+$/.test(h)) return false; 264 - if (h.startsWith('-') || h.endsWith('-')) return false; 265 - if (h.includes('--')) return false; 266 - if (RESERVED_HANDLES.has(h)) return false; 267 - return true; 268 - }; 269 - 270 - export const toDomain = (handle: string): string => `${handle.toLowerCase()}.${BASE_HOST}`; 271 268 272 269 export const getDomainByDid = async (did: string): Promise<string | null> => { 273 270 const rows = await db`SELECT domain FROM domains WHERE did = ${did} ORDER BY created_at ASC LIMIT 1`; ··· 606 603 607 604 console.log('[CookieSecret] Generated new cookie signing secret'); 608 605 return secret; 606 + }; 607 + 608 + export const getServiceIdentityKeypair = async (): Promise<{ 609 + publicKeyMultibase: string; 610 + privateKeyMultibase: string | null; 611 + } | null> => { 612 + const rows = await db` 613 + SELECT public_key_multibase, private_key_multibase 614 + FROM service_identity_keys 615 + WHERE id = 'default' 616 + LIMIT 1 617 + `; 618 + 619 + if (rows.length === 0) { 620 + return null; 621 + } 622 + 623 + return { 624 + publicKeyMultibase: rows[0].public_key_multibase as string, 625 + privateKeyMultibase: (rows[0].private_key_multibase as string | undefined) ?? null, 626 + }; 627 + }; 628 + 629 + export const setServiceIdentityKeypair = async ( 630 + publicKeyMultibase: string, 631 + privateKeyMultibase: string | null 632 + ): Promise<void> => { 633 + await db` 634 + INSERT INTO service_identity_keys (id, public_key_multibase, private_key_multibase, created_at, updated_at) 635 + VALUES ('default', ${publicKeyMultibase}, ${privateKeyMultibase}, EXTRACT(EPOCH FROM NOW()), EXTRACT(EPOCH FROM NOW())) 636 + ON CONFLICT (id) 637 + DO UPDATE SET 638 + public_key_multibase = EXCLUDED.public_key_multibase, 639 + private_key_multibase = EXCLUDED.private_key_multibase, 640 + updated_at = EXTRACT(EPOCH FROM NOW()) 641 + `; 609 642 }; 610 643 611 644 // Supporter management functions
+111
apps/main-app/src/lib/domain-utils.ts
··· 1 + import { BASE_HOST } from '@wispplace/constants' 2 + 3 + const RESERVED_HANDLES = new Set([ 4 + 'www', 5 + 'api', 6 + 'admin', 7 + 'static', 8 + 'public', 9 + 'preview', 10 + 'slingshot', 11 + 'plc', 12 + 'constellation', 13 + 'cdn', 14 + 'pds', 15 + 'staging', 16 + 'auth' 17 + ]) 18 + 19 + const RESERVED_CUSTOM_DOMAINS = new Set([ 20 + 'localhost', 21 + 'example.com', 22 + 'example.org', 23 + 'example.net', 24 + 'test', 25 + 'invalid', 26 + 'local' 27 + ]) 28 + 29 + const RESERVED_CUSTOM_PATTERNS = [ 30 + /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\./, 31 + /^(?:\d{1,3}\.){3}\d{1,3}$/ 32 + ] 33 + 34 + const CUSTOM_DOMAIN_PATTERN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/ 35 + 36 + export const normalizeDomain = (domain: string): string => { 37 + return domain.trim().toLowerCase().replace(/\.$/, '') 38 + } 39 + 40 + export const isValidHandle = (handle: string): boolean => { 41 + const normalized = handle.trim().toLowerCase() 42 + 43 + if (normalized.length < 3 || normalized.length > 63) return false 44 + if (!/^[a-z0-9-]+$/.test(normalized)) return false 45 + if (normalized.startsWith('-') || normalized.endsWith('-')) return false 46 + if (normalized.includes('--')) return false 47 + if (RESERVED_HANDLES.has(normalized)) return false 48 + 49 + return true 50 + } 51 + 52 + export const toDomain = (handle: string): string => `${handle.toLowerCase()}.${BASE_HOST}` 53 + 54 + export const extractWispHandle = (domain: string): string | null => { 55 + const normalized = normalizeDomain(domain) 56 + const suffix = `.${BASE_HOST}` 57 + 58 + if (!normalized.endsWith(suffix)) { 59 + return null 60 + } 61 + 62 + const handle = normalized.slice(0, -suffix.length) 63 + if (handle.length === 0 || handle.includes('.')) { 64 + return null 65 + } 66 + 67 + return handle 68 + } 69 + 70 + export const validateCustomDomain = (domain: string): string | null => { 71 + const normalized = normalizeDomain(domain) 72 + 73 + if (normalized.length < 3 || normalized.length > 253) { 74 + return 'domain must be 3-253 characters' 75 + } 76 + 77 + if (!CUSTOM_DOMAIN_PATTERN.test(normalized)) { 78 + return 'invalid domain format' 79 + } 80 + 81 + const labels = normalized.split('.') 82 + for (const label of labels) { 83 + if (label.length === 0 || label.length > 63) { 84 + return 'domain labels must be 1-63 characters' 85 + } 86 + if (label.startsWith('-') || label.endsWith('-')) { 87 + return 'domain labels cannot start or end with hyphen' 88 + } 89 + } 90 + 91 + const tld = labels[labels.length - 1] 92 + if (tld.length < 2 || /^\d+$/.test(tld)) { 93 + return 'tld must be at least 2 characters and not all numeric' 94 + } 95 + 96 + if (!/^[a-z0-9.-]+$/.test(normalized)) { 97 + return 'domain must use ascii alphanumeric characters, dots, and hyphens' 98 + } 99 + 100 + if (RESERVED_CUSTOM_DOMAINS.has(normalized)) { 101 + return 'reserved or blocked domain' 102 + } 103 + 104 + for (const pattern of RESERVED_CUSTOM_PATTERNS) { 105 + if (pattern.test(normalized)) { 106 + return 'ip addresses are not allowed' 107 + } 108 + } 109 + 110 + return null 111 + }
+72
apps/main-app/src/lib/service-identity.ts
··· 1 + import { P256PrivateKeyExportable } from '@atcute/crypto' 2 + 3 + import { createLogger } from '@wispplace/observability' 4 + 5 + import { getServiceIdentityKeypair, setServiceIdentityKeypair } from './db' 6 + 7 + const logger = createLogger('main-app') 8 + 9 + const validateMultikey = (value: string): string => { 10 + const trimmed = value.trim() 11 + if (!trimmed.startsWith('z')) { 12 + throw new Error('service key must be a multibase base58btc key') 13 + } 14 + 15 + return trimmed 16 + } 17 + 18 + export interface ServiceIdentityKeypair { 19 + publicKeyMultibase: string 20 + privateKeyMultibase: string 21 + } 22 + 23 + const generateAndPersistKeypair = async (): Promise<ServiceIdentityKeypair> => { 24 + const keypair = await P256PrivateKeyExportable.createKeypair() 25 + const publicKeyMultibase = validateMultikey(await keypair.exportPublicKey('multikey')) 26 + const privateKeyMultibase = validateMultikey(await keypair.exportPrivateKey('multikey')) 27 + 28 + await setServiceIdentityKeypair(publicKeyMultibase, privateKeyMultibase) 29 + logger.info('Generated new service identity keypair') 30 + 31 + return { 32 + publicKeyMultibase, 33 + privateKeyMultibase, 34 + } 35 + } 36 + 37 + export const ensureServiceIdentityKeypair = async ( 38 + configuredPublic?: string | null, 39 + configuredPrivate?: string | null, 40 + ): Promise<ServiceIdentityKeypair> => { 41 + const explicitPublic = configuredPublic ? validateMultikey(configuredPublic) : null 42 + const explicitPrivate = configuredPrivate ? validateMultikey(configuredPrivate) : null 43 + 44 + if ((explicitPublic && !explicitPrivate) || (!explicitPublic && explicitPrivate)) { 45 + throw new Error('both SERVICE_PUBLIC_KEY_MULTIBASE and SERVICE_PRIVATE_KEY_MULTIBASE must be set together') 46 + } 47 + 48 + if (explicitPublic && explicitPrivate) { 49 + await setServiceIdentityKeypair(explicitPublic, explicitPrivate) 50 + logger.info('Updated service identity keypair from environment') 51 + 52 + return { 53 + publicKeyMultibase: explicitPublic, 54 + privateKeyMultibase: explicitPrivate, 55 + } 56 + } 57 + 58 + const existing = await getServiceIdentityKeypair() 59 + if (!existing) { 60 + return generateAndPersistKeypair() 61 + } 62 + 63 + if (!existing.privateKeyMultibase) { 64 + logger.warn('Service identity record missing private key; generating replacement keypair') 65 + return generateAndPersistKeypair() 66 + } 67 + 68 + return { 69 + publicKeyMultibase: existing.publicKeyMultibase, 70 + privateKeyMultibase: existing.privateKeyMultibase, 71 + } 72 + }
+18 -74
apps/main-app/src/routes/domain.ts
··· 7 7 getDomainByDid, 8 8 isDomainAvailable, 9 9 isDomainRegistered, 10 - isValidHandle, 11 - toDomain, 12 10 updateDomain, 13 11 countWispDomains, 14 12 deleteWispDomain, ··· 20 18 updateWispDomainSite, 21 19 updateCustomDomainRkey 22 20 } from '../lib/db' 21 + import { 22 + extractWispHandle, 23 + isValidHandle, 24 + normalizeDomain, 25 + toDomain, 26 + validateCustomDomain 27 + } from '../lib/domain-utils' 23 28 import { createHash } from 'crypto' 24 29 import { verifyCustomDomain } from '../lib/dns-verify' 25 30 import { createLogger } from '@wispplace/observability' ··· 73 78 */ 74 79 .get('/registered', async ({ query, set }) => { 75 80 try { 76 - const domain = (query.domain || "").trim().toLowerCase(); 81 + const domain = normalizeDomain(String(query.domain || '')) 77 82 78 83 if (!domain) { 79 84 set.status = 400; ··· 206 211 .post('/custom/add', async ({ body, auth, set }) => { 207 212 try { 208 213 const { domain } = body as { domain: string }; 209 - const domainLower = domain.toLowerCase().trim(); 210 - 211 - // Enhanced domain validation 212 - // 1. Length check (RFC 1035: labels 1-63 chars, total max 253) 213 - if (!domainLower || domainLower.length < 3 || domainLower.length > 253) { 214 - set.status = 400 215 - throw new Error('Invalid domain: must be 3-253 characters'); 216 - } 217 - 218 - // 2. Basic format validation 219 - // - Must contain at least one dot (require TLD) 220 - // - Valid characters: a-z, 0-9, hyphen, dot 221 - // - No consecutive dots, no leading/trailing dots or hyphens 222 - const domainPattern = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/; 223 - if (!domainPattern.test(domainLower)) { 224 - set.status = 400 225 - throw new Error('Invalid domain format'); 226 - } 227 - 228 - // 3. Validate each label (part between dots) 229 - const labels = domainLower.split('.'); 230 - for (const label of labels) { 231 - if (label.length === 0 || label.length > 63) { 232 - set.status = 400 233 - throw new Error('Invalid domain: label length must be 1-63 characters'); 234 - } 235 - if (label.startsWith('-') || label.endsWith('-')) { 236 - set.status = 400 237 - throw new Error('Invalid domain: labels cannot start or end with hyphen'); 238 - } 239 - } 240 - 241 - // 4. TLD validation (require valid TLD, block single-char TLDs and numeric TLDs) 242 - const tld = labels[labels.length - 1]; 243 - if (tld.length < 2 || /^\d+$/.test(tld)) { 244 - set.status = 400 245 - throw new Error('Invalid domain: TLD must be at least 2 characters and not all numeric'); 246 - } 247 - 248 - // 5. Homograph attack protection - block domains with mixed scripts or confusables 249 - // Block non-ASCII characters (Punycode domains should be pre-converted) 250 - if (!/^[a-z0-9.-]+$/.test(domainLower)) { 251 - set.status = 400 252 - throw new Error('Invalid domain: only ASCII alphanumeric, dots, and hyphens allowed'); 253 - } 254 - 255 - // 6. Block localhost, internal IPs, and reserved domains 256 - const blockedDomains = [ 257 - 'localhost', 258 - 'example.com', 259 - 'example.org', 260 - 'example.net', 261 - 'test', 262 - 'invalid', 263 - 'local' 264 - ]; 265 - const blockedPatterns = [ 266 - /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\./, // Private IPs 267 - /^(?:\d{1,3}\.){3}\d{1,3}$/, // Any IP address 268 - ]; 214 + const domainLower = normalizeDomain(domain || '') 269 215 270 - if (blockedDomains.includes(domainLower)) { 216 + const domainError = validateCustomDomain(domainLower) 217 + if (domainError) { 271 218 set.status = 400 272 - throw new Error('Invalid domain: reserved or blocked domain'); 273 - } 274 - 275 - for (const pattern of blockedPatterns) { 276 - if (pattern.test(domainLower)) { 277 - set.status = 400 278 - throw new Error('Invalid domain: IP addresses not allowed'); 279 - } 219 + throw new Error(`Invalid domain: ${domainError}`) 280 220 } 281 221 282 222 // Check if already exists and is verified ··· 396 336 const { domain } = params; 397 337 398 338 // Verify domain belongs to user 399 - const domainLower = domain.toLowerCase().trim(); 339 + const domainLower = normalizeDomain(domain) 400 340 const info = await isDomainRegistered(domainLower); 401 341 402 342 if (!info.registered || info.type !== 'wisp') { ··· 414 354 415 355 // Delete from PDS 416 356 const agent = new Agent((url, init) => auth.session.fetchHandler(url, init)); 417 - const handle = domainLower.replace(`.${process.env.BASE_DOMAIN || 'wisp.place'}`, ''); 357 + const handle = extractWispHandle(domainLower); 358 + if (!handle) { 359 + set.status = 400 360 + throw new Error('Invalid wisp domain'); 361 + } 418 362 try { 419 363 await agent.com.atproto.repo.deleteRecord({ 420 364 repo: auth.did,
+453
apps/main-app/src/routes/xrpc.ts
··· 1 + import { createHash } from 'crypto'; 2 + 3 + import { Elysia } from 'elysia'; 4 + 5 + import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver } from '@atcute/identity-resolver'; 6 + import { json, XRPCRouter, XRPCError } from '@atcute/xrpc-server'; 7 + import { ServiceJwtVerifier } from '@atcute/xrpc-server/auth'; 8 + import { PlaceWispV2DomainClaim, PlaceWispV2DomainGetStatus } from '@wispplace/lexicons/atcute'; 9 + import { BASE_HOST } from '@wispplace/constants'; 10 + 11 + import { createLogger } from '@wispplace/observability'; 12 + 13 + import { 14 + claimCustomDomain, 15 + claimDomain, 16 + getCustomDomainInfo, 17 + isDomainRegistered, 18 + updateCustomDomainRkey, 19 + updateWispDomainSite, 20 + } from '../lib/db'; 21 + import { 22 + extractWispHandle, 23 + isValidHandle, 24 + normalizeDomain, 25 + validateCustomDomain, 26 + } from '../lib/domain-utils'; 27 + 28 + const logger = createLogger('main-app'); 29 + const isLocalDev = Bun.env.LOCAL_DEV === 'true'; 30 + 31 + interface XrpcAuthContext { 32 + did: string; 33 + } 34 + 35 + const DEFAULT_SERVICE_DID = isLocalDev ? 'did:web:localhost' : 'did:web:wisp.place'; 36 + const SERVICE_DID = Bun.env.SERVICE_DID ?? DEFAULT_SERVICE_DID; 37 + const serviceJwtVerifier = new ServiceJwtVerifier({ 38 + serviceDid: SERVICE_DID as any, 39 + resolver: new CompositeDidDocumentResolver({ 40 + methods: { 41 + plc: new PlcDidDocumentResolver(), 42 + web: new WebDidDocumentResolver(), 43 + }, 44 + }), 45 + }); 46 + 47 + const NSID_ALIASES: Record<string, string> = { 48 + 'place.wisp.v2.domain.getstatus': 'place.wisp.v2.domain.getStatus', 49 + 'place.wisp.v2.domain.get-status': 'place.wisp.v2.domain.getStatus', 50 + }; 51 + 52 + const toIsoFromEpoch = (epoch: unknown): string | undefined => { 53 + let numeric: number | undefined; 54 + 55 + if (typeof epoch === 'number') { 56 + numeric = epoch; 57 + } else if (typeof epoch === 'string') { 58 + numeric = Number(epoch); 59 + } 60 + 61 + if (!Number.isFinite(numeric)) { 62 + return undefined; 63 + } 64 + 65 + const ms = numeric! < 1_000_000_000_000 ? numeric! * 1000 : numeric!; 66 + const date = new Date(ms); 67 + 68 + if (Number.isNaN(date.getTime())) { 69 + return undefined; 70 + } 71 + 72 + return date.toISOString(); 73 + }; 74 + 75 + type DidString = `did:${string}:${string}`; 76 + 77 + const buildCustomDnsInstructions = (domain: string, did: DidString, challengeId: string) => { 78 + return { 79 + challengeId, 80 + txtName: `_wisp.${domain}`, 81 + txtValue: did, 82 + cnameTarget: `${challengeId}.dns.${BASE_HOST}`, 83 + }; 84 + }; 85 + 86 + const authRequired = (): never => { 87 + throw new XRPCError({ status: 401, error: 'AuthenticationRequired', description: 'authentication required' }); 88 + }; 89 + 90 + const invalidDomain = (description: string): never => { 91 + throw new XRPCError({ 92 + status: 400, 93 + error: 'InvalidDomain', 94 + description, 95 + }); 96 + }; 97 + 98 + const alreadyClaimed = (description: string): never => { 99 + throw new XRPCError({ 100 + status: 409, 101 + error: 'AlreadyClaimed', 102 + description, 103 + }); 104 + }; 105 + 106 + const domainLimitReached = (): never => { 107 + throw new XRPCError({ 108 + status: 400, 109 + error: 'DomainLimitReached', 110 + description: 'free tier users can claim up to 3 wisp subdomains', 111 + }); 112 + }; 113 + 114 + const requireAuthenticated = (auth: XrpcAuthContext | undefined): XrpcAuthContext => { 115 + if (!auth) { 116 + authRequired(); 117 + } 118 + 119 + return auth!; 120 + }; 121 + 122 + const authRequiredWith = (description: string): never => { 123 + throw new XRPCError({ status: 401, error: 'AuthenticationRequired', description }); 124 + }; 125 + 126 + const resolveServiceAuth = async (request: Request, nsid: string): Promise<XrpcAuthContext | undefined> => { 127 + const authorization = request.headers.get('authorization'); 128 + if (!authorization) { 129 + return undefined; 130 + } 131 + 132 + if (!authorization.startsWith('Bearer ')) { 133 + authRequiredWith('missing or invalid authorization header'); 134 + } 135 + 136 + const jwt = authorization.slice('Bearer '.length).trim(); 137 + if (!jwt) { 138 + authRequiredWith('missing service authorization token'); 139 + } 140 + 141 + const result = await serviceJwtVerifier.verify(jwt, { lxm: nsid as any }); 142 + if (!result.ok) { 143 + authRequiredWith(result.error.description); 144 + throw new Error('unreachable'); 145 + } 146 + 147 + return { did: result.value.issuer }; 148 + }; 149 + 150 + const serializeBodyForForwarding = (body: unknown): string | undefined => { 151 + if (body === undefined || body === null) { 152 + return undefined; 153 + } 154 + 155 + if (typeof body === 'string') { 156 + return body; 157 + } 158 + 159 + return JSON.stringify(body); 160 + }; 161 + 162 + const prepareXrpcRequest = async (request: Request, parsedBody: unknown): Promise<Request> => { 163 + if (request.method === 'GET' || request.method === 'HEAD') { 164 + return request; 165 + } 166 + 167 + const headers = new Headers(request.headers); 168 + headers.delete('content-length'); 169 + 170 + let bodyText: string | undefined; 171 + 172 + if (!request.bodyUsed) { 173 + try { 174 + bodyText = await request.text(); 175 + } catch { 176 + bodyText = undefined; 177 + } 178 + } 179 + 180 + if (bodyText === undefined) { 181 + bodyText = serializeBodyForForwarding(parsedBody); 182 + if (bodyText !== undefined && !headers.has('content-type')) { 183 + headers.set('content-type', 'application/json'); 184 + } 185 + } 186 + 187 + return new Request(request.url, { 188 + method: request.method, 189 + headers, 190 + body: bodyText, 191 + }); 192 + }; 193 + 194 + const normalizeNsidPath = (request: Request): { request: Request; rawNsid: string; nsid: string } => { 195 + const url = new URL(request.url); 196 + const rawNsid = url.pathname.startsWith('/xrpc/') ? url.pathname.slice('/xrpc/'.length) : url.pathname; 197 + const nsid = NSID_ALIASES[rawNsid] ?? rawNsid; 198 + 199 + if (nsid === rawNsid) { 200 + return { request, rawNsid, nsid }; 201 + } 202 + 203 + url.pathname = `/xrpc/${nsid}`; 204 + 205 + return { 206 + request: new Request(url.toString(), request), 207 + rawNsid, 208 + nsid, 209 + }; 210 + }; 211 + 212 + export const xrpcRoutes = () => { 213 + const authByRequest = new WeakMap<Request, XrpcAuthContext>(); 214 + const router = new XRPCRouter(); 215 + 216 + router.addQuery(PlaceWispV2DomainGetStatus.mainSchema, { 217 + async handler({ params, request }) { 218 + const domain = normalizeDomain(params.domain); 219 + const auth = authByRequest.get(request); 220 + 221 + if (domain.length === 0) { 222 + invalidDomain('domain parameter is required'); 223 + } 224 + 225 + const info = await isDomainRegistered(domain); 226 + if (!info.registered) { 227 + return json({ 228 + domain, 229 + status: 'unclaimed', 230 + }); 231 + } 232 + 233 + const kind = info.type; 234 + const ownedByCaller = auth ? auth.did === info.did : undefined; 235 + 236 + if (auth && ownedByCaller === false) { 237 + return json({ 238 + domain, 239 + kind, 240 + status: 'alreadyClaimed', 241 + verified: kind === 'custom' ? Boolean(info.verified) : true, 242 + siteRkey: info.rkey ?? undefined, 243 + }); 244 + } 245 + 246 + if (kind === 'custom') { 247 + const custom = await getCustomDomainInfo(domain); 248 + const verified = Boolean(custom?.verified); 249 + 250 + return json({ 251 + domain, 252 + kind, 253 + status: verified ? 'verified' : 'pendingVerification', 254 + verified, 255 + siteRkey: info.rkey ?? undefined, 256 + lastCheckedAt: toIsoFromEpoch(custom?.last_verified_at), 257 + }); 258 + } 259 + 260 + return json({ 261 + domain, 262 + kind, 263 + status: 'verified', 264 + verified: true, 265 + siteRkey: info.rkey ?? undefined, 266 + }); 267 + }, 268 + }); 269 + 270 + router.addProcedure(PlaceWispV2DomainClaim.mainSchema, { 271 + async handler({ input, request }) { 272 + const auth = requireAuthenticated(authByRequest.get(request)); 273 + const did = auth.did as DidString; 274 + 275 + const domain = normalizeDomain(input.domain); 276 + if (domain.length === 0) { 277 + invalidDomain('domain is required'); 278 + } 279 + 280 + const wispHandle = extractWispHandle(domain); 281 + if (wispHandle !== null) { 282 + if (!isValidHandle(wispHandle)) { 283 + invalidDomain('invalid wisp subdomain handle'); 284 + } 285 + 286 + const existing = await isDomainRegistered(domain); 287 + if (existing.registered && existing.did !== did) { 288 + alreadyClaimed('domain is already claimed'); 289 + } 290 + 291 + if (existing.registered && existing.did === did) { 292 + if (input.siteRkey !== undefined) { 293 + await updateWispDomainSite(domain, input.siteRkey); 294 + } 295 + 296 + return json({ 297 + domain, 298 + kind: 'wisp', 299 + status: 'alreadyClaimed', 300 + siteRkey: input.siteRkey ?? existing.rkey ?? undefined, 301 + }); 302 + } 303 + 304 + try { 305 + await claimDomain(did, wispHandle); 306 + } catch (err) { 307 + const message = err instanceof Error ? err.message : ''; 308 + 309 + if (message === 'domain_limit_reached') { 310 + domainLimitReached(); 311 + } 312 + if (message === 'invalid_handle') { 313 + invalidDomain('invalid wisp subdomain handle'); 314 + } 315 + 316 + alreadyClaimed('domain is already claimed'); 317 + } 318 + 319 + if (input.siteRkey !== undefined) { 320 + await updateWispDomainSite(domain, input.siteRkey); 321 + } 322 + 323 + return json({ 324 + domain, 325 + kind: 'wisp', 326 + status: 'verified', 327 + siteRkey: input.siteRkey, 328 + }); 329 + } 330 + 331 + const customError = validateCustomDomain(domain); 332 + if (customError !== null) { 333 + invalidDomain(customError); 334 + } 335 + 336 + const existing = await getCustomDomainInfo(domain); 337 + if (existing && existing.verified && existing.did !== did) { 338 + alreadyClaimed('domain already verified and owned by another user'); 339 + } 340 + 341 + if (existing && existing.did === did) { 342 + if (input.siteRkey !== undefined) { 343 + await updateCustomDomainRkey(existing.id, input.siteRkey); 344 + } 345 + 346 + const status = existing.verified ? 'verified' : 'pendingVerification'; 347 + 348 + return json({ 349 + domain, 350 + kind: 'custom', 351 + status, 352 + siteRkey: input.siteRkey ?? existing.rkey ?? undefined, 353 + ...buildCustomDnsInstructions(domain, did, existing.id), 354 + }); 355 + } 356 + 357 + const challengeId = createHash('sha256').update(`${did}:${domain}`).digest('hex').substring(0, 16); 358 + 359 + try { 360 + await claimCustomDomain(did, domain, challengeId, input.siteRkey ?? null); 361 + } catch (err) { 362 + alreadyClaimed('domain already verified and owned by another user'); 363 + } 364 + 365 + return json({ 366 + domain, 367 + kind: 'custom', 368 + status: 'pendingVerification', 369 + siteRkey: input.siteRkey, 370 + ...buildCustomDnsInstructions(domain, did, challengeId), 371 + }); 372 + }, 373 + }); 374 + 375 + return new Elysia().all('/xrpc/*', async ({ body, request }) => { 376 + const startedAt = Date.now(); 377 + let xrpcRequest: Request | undefined; 378 + let nsid = ''; 379 + let rawNsid = ''; 380 + let auth: XrpcAuthContext | undefined; 381 + 382 + try { 383 + const preparedRequest = await prepareXrpcRequest(request, body); 384 + const normalized = normalizeNsidPath(preparedRequest); 385 + xrpcRequest = normalized.request; 386 + rawNsid = normalized.rawNsid; 387 + nsid = normalized.nsid; 388 + 389 + const authorization = xrpcRequest.headers.get('authorization'); 390 + logger.info('[XRPC] Incoming request', { 391 + method: xrpcRequest.method, 392 + rawNsid, 393 + nsid, 394 + origin: xrpcRequest.headers.get('origin') ?? undefined, 395 + hasAuthorization: Boolean(authorization), 396 + authorizationScheme: authorization ? authorization.split(' ')[0] : undefined, 397 + }); 398 + 399 + auth = await resolveServiceAuth(xrpcRequest, nsid); 400 + if (auth) { 401 + authByRequest.set(xrpcRequest, auth); 402 + } 403 + 404 + const response = await router.fetch(xrpcRequest); 405 + 406 + if (!response.ok) { 407 + let responseData: unknown; 408 + try { 409 + responseData = await response.clone().json(); 410 + } catch { 411 + responseData = await response.clone().text(); 412 + } 413 + 414 + logger.warn('[XRPC] Request failed', { 415 + method: xrpcRequest.method, 416 + rawNsid, 417 + nsid, 418 + status: response.status, 419 + did: auth?.did, 420 + origin: xrpcRequest.headers.get('origin') ?? undefined, 421 + requestBodyUsed: request.bodyUsed, 422 + error: responseData, 423 + durationMs: Date.now() - startedAt, 424 + }); 425 + } else { 426 + logger.info('[XRPC] Request succeeded', { 427 + method: xrpcRequest.method, 428 + rawNsid, 429 + nsid, 430 + status: response.status, 431 + did: auth?.did, 432 + durationMs: Date.now() - startedAt, 433 + }); 434 + } 435 + 436 + return response; 437 + } catch (err) { 438 + logger.error('[XRPC] Handler error', { 439 + method: xrpcRequest?.method ?? request.method, 440 + rawNsid: rawNsid || undefined, 441 + nsid: nsid || undefined, 442 + origin: request.headers.get('origin') ?? undefined, 443 + durationMs: Date.now() - startedAt, 444 + error: err instanceof Error ? err.message : String(err), 445 + }); 446 + throw err; 447 + } finally { 448 + if (xrpcRequest) { 449 + authByRequest.delete(xrpcRequest); 450 + } 451 + } 452 + }); 453 + };
+86 -13
bun.lock
··· 78 78 "name": "@wispplace/main-app", 79 79 "version": "1.0.50", 80 80 "dependencies": { 81 + "@atcute/crypto": "^2.3.0", 82 + "@atcute/identity": "^1.1.3", 83 + "@atcute/identity-resolver": "^1.2.2", 84 + "@atcute/oauth-crypto": "^0.1.0", 85 + "@atcute/xrpc-server": "^0.1.10", 81 86 "@atproto-labs/did-resolver": "^0.2.4", 82 87 "@atproto/api": "^0.17.7", 83 88 "@atproto/common-web": "^0.4.6", ··· 225 230 "name": "@wispplace/lexicons", 226 231 "version": "1.0.0", 227 232 "dependencies": { 233 + "@atcute/lexicons": "^1.2.9", 228 234 "@atproto/lexicon": "^0.5.1", 229 235 "@atproto/xrpc-server": "^0.9.5", 230 236 }, 231 237 "devDependencies": { 238 + "@atcute/lex-cli": "^2.5.3", 232 239 "@atproto/lex-cli": "^0.9.5", 233 240 "multiformats": "^13.4.1", 234 241 }, ··· 303 310 304 311 "@atcute/bluesky": ["@atcute/bluesky@3.2.15", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.6" } }, "sha512-H4RW3WffjfdKvOZ9issEUQnuSR4KfuAwwJnYu0fclA9VDa99JTJ+pa8tTl9lFeBV9DINtWJAx7rdIbICoVCstQ=="], 305 312 313 + "@atcute/car": ["@atcute/car@5.1.1", "", { "dependencies": { "@atcute/cbor": "^2.3.2", "@atcute/cid": "^2.4.1", "@atcute/uint8array": "^1.1.1", "@atcute/varint": "^2.0.0" } }, "sha512-MeRUJNXYgAHrJZw7mMoZJb9xIqv3LZLQw90rRRAVAo8SGNdICwyqe6Bf2LGesX73QM04MBuYO6Kqhvold3TFfg=="], 314 + 315 + "@atcute/cbor": ["@atcute/cbor@2.3.2", "", { "dependencies": { "@atcute/cid": "^2.4.1", "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1" } }, "sha512-xP2SORSau/VVI00x2V4BjwIkHr6EQ7l/MXEOPaa4LGYtePFc4gnD4L1yN10dT5NEuUnvGEuCh6arLB7gz1smVQ=="], 316 + 317 + "@atcute/cid": ["@atcute/cid@2.4.1", "", { "dependencies": { "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1" } }, "sha512-bwhna69RCv7yetXudtj+2qrMPYvhhIQqvJz6YUpUS98v7OdF3X2dnye9Nig2NDrklZcuyOsu7sQo7GOykJXRLQ=="], 318 + 306 319 "@atcute/client": ["@atcute/client@4.2.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.6" } }, "sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw=="], 320 + 321 + "@atcute/crypto": ["@atcute/crypto@2.3.0", "", { "dependencies": { "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6", "@noble/secp256k1": "^3.0.0" } }, "sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ=="], 307 322 308 323 "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 309 324 310 325 "@atcute/identity-resolver": ["@atcute/identity-resolver@1.2.2", "", { "dependencies": { "@atcute/lexicons": "^1.2.6", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw=="], 311 326 312 - "@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 327 + "@atcute/lex-cli": ["@atcute/lex-cli@2.5.3", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/identity-resolver": "^1.2.2", "@atcute/lexicon-doc": "^2.1.0", "@atcute/lexicon-resolver": "^0.1.6", "@atcute/lexicons": "^1.2.7", "@badrap/valita": "^0.4.6", "@optique/core": "^0.6.10", "@optique/run": "^0.6.10", "picocolors": "^1.1.1", "prettier": "^3.7.4" }, "bin": { "lex-cli": "cli.mjs" } }, "sha512-829rvezMOfRkJQRKvupNT8TWT/YYffJ2QsB80D9aPjkXSogrETZA7xZcPaMZBXg+mJaVbLO9S4ThPQmlF0L4UQ=="], 328 + 329 + "@atcute/lexicon-doc": ["@atcute/lexicon-doc@2.1.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.9", "@atcute/uint8array": "^1.1.1", "@atcute/util-text": "^1.1.1", "@badrap/valita": "^0.4.6" } }, "sha512-DirteHRK0GPLFVsIkaxD2IxUQUtpMO3I/EM8gy+2HAn6nODfN85qDYMefE2oA/QvTa97SVkXaoFNzZkCshx7+g=="], 330 + 331 + "@atcute/lexicon-resolver": ["@atcute/lexicon-resolver@0.1.6", "", { "dependencies": { "@atcute/crypto": "^2.3.0", "@atcute/lexicon-doc": "^2.0.6", "@atcute/lexicons": "^1.2.6", "@atcute/repo": "^0.1.1", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.1.0", "@atcute/identity-resolver": "^1.1.3" } }, "sha512-wJC/ChmpP7k+ywpOd07CMvioXjIGaFpF3bDwXLi/086LYjSWHOvtW6pyC+mqP5wLhjyH2hn4wmi77Buew1l1aw=="], 332 + 333 + "@atcute/lexicons": ["@atcute/lexicons@1.2.9", "", { "dependencies": { "@atcute/uint8array": "^1.1.1", "@atcute/util-text": "^1.1.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-/RRHm2Cw9o8Mcsrq0eo8fjS9okKYLGfuFwrQ0YoP/6sdSDsXshaTLJsvLlcUcaDaSJ1YFOuHIo3zr2Om2F/16g=="], 334 + 335 + "@atcute/mst": ["@atcute/mst@0.1.2", "", { "dependencies": { "@atcute/cbor": "^2.3.0", "@atcute/cid": "^2.4.0", "@atcute/uint8array": "^1.0.6" } }, "sha512-Oz5CZTjqauEJLT9B+zkoy/mjl216DrjCxJFrguRV3N+1NkIbCfAcSRf3UDSNjfzDzBkJvC1WjA/3oQkm83duPg=="], 336 + 337 + "@atcute/multibase": ["@atcute/multibase@1.1.8", "", { "dependencies": { "@atcute/uint8array": "^1.1.1" } }, "sha512-pJgtImMZKCjqwRbu+2GzB+4xQjKBXDwdZOzeqe0u97zYKRGftpGYGvYv3+pMe2xXe+msDyu7Nv8iJp+U14otTA=="], 338 + 339 + "@atcute/oauth-crypto": ["@atcute/oauth-crypto@0.1.0", "", { "dependencies": { "@atcute/multibase": "^1.1.7", "@atcute/uint8array": "^1.1.0", "@badrap/valita": "^0.4.6", "nanoid": "^5.1.6" } }, "sha512-qZYDCNLF/4B6AndYT1rsQelN8621AC5u/sL5PHvlr/qqAbmmUwCBGjEgRSyZtHE1AqD60VNiSMlOgAuEQTSl3w=="], 340 + 341 + "@atcute/repo": ["@atcute/repo@0.1.2", "", { "dependencies": { "@atcute/car": "^5.1.1", "@atcute/cbor": "^2.3.2", "@atcute/cid": "^2.4.1", "@atcute/crypto": "^2.3.0", "@atcute/lexicons": "^1.2.9", "@atcute/mst": "^0.1.2", "@atcute/uint8array": "^1.1.1" } }, "sha512-mX/k8Nv7XFBbahcz5+qsdY91DVwKe8wbut/BrrmzClmSaUgKpztsHjtNfBCamcvIUKc18Lyv8WcVWzlH9wSf5w=="], 313 342 314 343 "@atcute/tangled": ["@atcute/tangled@1.0.14", "", { "dependencies": { "@atcute/atproto": "^3.1.10", "@atcute/lexicons": "^1.2.6" } }, "sha512-vOJuFUCI/dova2OL5kPFgMzYLFyzFNzxxLCvtPvukgcr7ZIm4qLGkNHdVg3Jk8c0bknULe0pJnaCKUTWA1VPdA=="], 315 344 316 - "@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 345 + "@atcute/uint8array": ["@atcute/uint8array@1.1.1", "", {}, "sha512-3LsC8XB8TKe9q/5hOA5sFuzGaIFdJZJNewC5OKa3o/eU6+K7JR6see9Zy2JbQERNVnRl11EzbNov1efgLMAs4g=="], 317 346 318 347 "@atcute/util-fetch": ["@atcute/util-fetch@1.0.5", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig=="], 319 348 320 - "@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 349 + "@atcute/util-text": ["@atcute/util-text@1.1.1", "", { "dependencies": { "unicode-segmenter": "^0.14.5" } }, "sha512-JH0SxzUQJAmbOBTYyhxQbkkI6M33YpjlVLEcbP5GYt43xgFArzV0FJVmEpvIj0kjsmphHB45b6IitdvxPdec9w=="], 350 + 351 + "@atcute/varint": ["@atcute/varint@2.0.0", "", {}, "sha512-CEY/oVK/nVpL4e5y3sdenLETDL6/Xu5xsE/0TupK+f0Yv8jcD60t2gD8SHROWSvUwYLdkjczLCSA7YrtnjCzWw=="], 352 + 353 + "@atcute/xrpc-server": ["@atcute/xrpc-server@0.1.10", "", { "dependencies": { "@atcute/cbor": "^2.3.2", "@atcute/crypto": "^2.3.0", "@atcute/identity": "^1.1.3", "@atcute/identity-resolver": "^1.2.2", "@atcute/lexicons": "^1.2.9", "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1", "@badrap/valita": "^0.4.6", "nanoid": "^5.1.6" } }, "sha512-j4I+ajiYn9Oxv8h5jU7Bzv1G9x7GNcsn7xpnFT7gXh4RUq19kyoKnKDToaQ+eHyCUo+lhqJOBDbggCdiC1SuGQ=="], 321 354 322 355 "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.5", "", { "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" } }, "sha512-he7EC6OMSifNs01a4RT9mta/yYitoKDzlK9ty2TFV5Uj/+HpB4vYMRdIDFrRW0Hcsehy90E2t/dw0t7361MEKQ=="], 323 356 ··· 601 634 602 635 "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], 603 636 637 + "@noble/secp256k1": ["@noble/secp256k1@3.0.0", "", {}, "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg=="], 638 + 604 639 "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], 605 640 606 641 "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q=="], ··· 657 692 658 693 "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], 659 694 695 + "@optique/core": ["@optique/core@0.6.11", "", {}, "sha512-GVLFihzBA1j78NFlkU5N1Lu0jRqET0k6Z66WK8VQKG/a3cxmCInVGSKMIdQG8i6pgC8wD5OizF6Y3QMztmhAxg=="], 696 + 697 + "@optique/run": ["@optique/run@0.6.11", "", { "dependencies": { "@optique/core": "0.6.11" } }, "sha512-tsXBEygGSzNpFK2gjsRlXBn7FiScUeLFWIZNpoAZ8iG85Km0/3K9xgqlQAXoQ+uEZBe4XplnzyCDvmEgbyNT8w=="], 698 + 660 699 "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw=="], 661 700 662 701 "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA=="], ··· 1101 1140 1102 1141 "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], 1103 1142 1104 - "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], 1143 + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], 1105 1144 1106 1145 "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], 1107 1146 ··· 1435 1474 1436 1475 "multiformats": ["multiformats@13.4.2", "", {}, "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ=="], 1437 1476 1438 - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 1477 + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], 1439 1478 1440 1479 "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 1441 1480 ··· 1743 1782 1744 1783 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 1745 1784 1785 + "@atcute/atproto/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1786 + 1787 + "@atcute/bluesky/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1788 + 1789 + "@atcute/client/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1790 + 1791 + "@atcute/identity/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1792 + 1793 + "@atcute/identity-resolver/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1794 + 1795 + "@atcute/tangled/@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 1796 + 1746 1797 "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], 1747 1798 1748 1799 "@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], ··· 1933 1984 1934 1985 "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1935 1986 1936 - "bun-types/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 1937 - 1938 1987 "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 1939 1988 1940 1989 "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], ··· 1950 1999 "iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 1951 2000 1952 2001 "pino-abstract-transport/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], 2002 + 2003 + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 1953 2004 1954 2005 "protobufjs/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 1955 2006 ··· 1985 2036 1986 2037 "wispctl/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 1987 2038 2039 + "@atcute/atproto/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2040 + 2041 + "@atcute/atproto/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2042 + 2043 + "@atcute/bluesky/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2044 + 2045 + "@atcute/bluesky/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2046 + 2047 + "@atcute/client/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2048 + 2049 + "@atcute/client/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2050 + 2051 + "@atcute/identity-resolver/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2052 + 2053 + "@atcute/identity-resolver/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2054 + 2055 + "@atcute/identity/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2056 + 2057 + "@atcute/identity/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2058 + 2059 + "@atcute/tangled/@atcute/lexicons/@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 2060 + 2061 + "@atcute/tangled/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2062 + 1988 2063 "@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.4", "", { "dependencies": { "@atproto/common": "^0.5.3", "ws": "^8.12.0" } }, "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg=="], 1989 2064 1990 2065 "@atproto/sync/@atproto/xrpc-server/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], ··· 2059 2134 2060 2135 "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 2061 2136 2062 - "@wispplace/bun-firehose/@types/bun/bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], 2063 - 2064 2137 "@wispplace/lexicons/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2065 2138 2066 2139 "@wispplace/main-app/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ=="], 2067 2140 2068 2141 "@wispplace/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2069 2142 2070 - "@wispplace/tiered-storage/@types/bun/bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], 2071 - 2072 2143 "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2073 2144 2074 2145 "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 2075 - 2076 - "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2077 2146 2078 2147 "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 2079 2148 ··· 2201 2270 2202 2271 "wisp-hosting-service/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2203 2272 2273 + "wisp-hosting-service/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], 2274 + 2204 2275 "wisp-hosting-service/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2205 2276 2206 2277 "wisp-hosting-service/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2278 + 2279 + "wispctl/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], 2207 2280 2208 2281 "wispctl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2209 2282
+84
lexicons/domain-claim-v2.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "place.wisp.v2.domain.claim", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Claim a domain for the authenticated DID. Returns DNS setup instructions for custom domains.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["domain"], 13 + "properties": { 14 + "domain": { 15 + "type": "string", 16 + "description": "Domain to claim (wisp subdomain FQDN or custom domain FQDN).", 17 + "minLength": 3, 18 + "maxLength": 253 19 + }, 20 + "siteRkey": { 21 + "type": "string", 22 + "format": "record-key", 23 + "description": "Optional place.wisp.fs rkey to map immediately after claim." 24 + } 25 + } 26 + } 27 + }, 28 + "output": { 29 + "encoding": "application/json", 30 + "schema": { 31 + "type": "object", 32 + "required": ["domain", "status"], 33 + "properties": { 34 + "domain": { 35 + "type": "string" 36 + }, 37 + "kind": { 38 + "type": "string", 39 + "enum": ["wisp", "custom"] 40 + }, 41 + "status": { 42 + "type": "string", 43 + "enum": ["alreadyClaimed", "pendingVerification", "verified"] 44 + }, 45 + "challengeId": { 46 + "type": "string", 47 + "description": "Identifier used to construct DNS challenge targets for custom domains.", 48 + "minLength": 8, 49 + "maxLength": 64 50 + }, 51 + "txtName": { 52 + "type": "string", 53 + "description": "TXT hostname to set for ownership proof (custom domains).", 54 + "minLength": 3, 55 + "maxLength": 253 56 + }, 57 + "txtValue": { 58 + "type": "string", 59 + "format": "did", 60 + "description": "TXT value to set for ownership proof (custom domains)." 61 + }, 62 + "cnameTarget": { 63 + "type": "string", 64 + "description": "Advisory CNAME target (custom domains).", 65 + "minLength": 3, 66 + "maxLength": 253 67 + }, 68 + "siteRkey": { 69 + "type": "string", 70 + "format": "record-key" 71 + } 72 + } 73 + } 74 + }, 75 + "errors": [ 76 + { "name": "AuthenticationRequired" }, 77 + { "name": "InvalidDomain" }, 78 + { "name": "AlreadyClaimed" }, 79 + { "name": "DomainLimitReached" }, 80 + { "name": "RateLimitExceeded" } 81 + ] 82 + } 83 + } 84 + }
+57
lexicons/domain-get-status-v2.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "place.wisp.v2.domain.getStatus", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get current claim and verification status for a domain.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["domain"], 11 + "properties": { 12 + "domain": { 13 + "type": "string", 14 + "description": "Domain to inspect (FQDN, lowercase preferred).", 15 + "minLength": 3, 16 + "maxLength": 253 17 + } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["domain", "status"], 25 + "properties": { 26 + "domain": { 27 + "type": "string" 28 + }, 29 + "status": { 30 + "type": "string", 31 + "enum": ["unclaimed", "pendingVerification", "verified", "alreadyClaimed"] 32 + }, 33 + "kind": { 34 + "type": "string", 35 + "enum": ["wisp", "custom"] 36 + }, 37 + "verified": { 38 + "type": "boolean" 39 + }, 40 + "lastCheckedAt": { 41 + "type": "string", 42 + "format": "datetime" 43 + }, 44 + "lastError": { 45 + "type": "string", 46 + "maxLength": 1000 47 + }, 48 + "siteRkey": { 49 + "type": "string", 50 + "format": "record-key" 51 + } 52 + } 53 + } 54 + } 55 + } 56 + } 57 + }
+105
lexicons/domains-v2.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "place.wisp.v2.domains", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Domain registration metadata for wisp.place subdomains and custom domains.", 8 + "key": "any", 9 + "record": { 10 + "type": "object", 11 + "required": ["domain", "registration", "createdAt", "updatedAt"], 12 + "properties": { 13 + "domain": { 14 + "type": "string", 15 + "description": "Lowercase FQDN for this registration (for example, alice.wisp.place or example.com).", 16 + "minLength": 3, 17 + "maxLength": 253 18 + }, 19 + "registration": { 20 + "type": "union", 21 + "refs": ["#wispRegistration", "#customRegistration"] 22 + }, 23 + "siteRkey": { 24 + "type": "string", 25 + "format": "record-key", 26 + "description": "Optional place.wisp.fs record key currently mapped to this domain." 27 + }, 28 + "createdAt": { 29 + "type": "string", 30 + "format": "datetime" 31 + }, 32 + "updatedAt": { 33 + "type": "string", 34 + "format": "datetime" 35 + } 36 + } 37 + } 38 + }, 39 + "wispRegistration": { 40 + "type": "object", 41 + "description": "Registration for a first-party subdomain under the wisp.place base host.", 42 + "required": ["kind", "handle"], 43 + "properties": { 44 + "kind": { 45 + "type": "string", 46 + "const": "wisp" 47 + }, 48 + "handle": { 49 + "type": "string", 50 + "description": "Subdomain label only (for example, alice).", 51 + "minLength": 3, 52 + "maxLength": 63 53 + } 54 + } 55 + }, 56 + "customRegistration": { 57 + "type": "object", 58 + "description": "Registration metadata for a custom domain.", 59 + "required": ["kind", "challengeId", "verification"], 60 + "properties": { 61 + "kind": { 62 + "type": "string", 63 + "const": "custom" 64 + }, 65 + "challengeId": { 66 + "type": "string", 67 + "description": "Challenge identifier used to derive DNS setup instructions.", 68 + "minLength": 8, 69 + "maxLength": 64 70 + }, 71 + "verification": { 72 + "type": "ref", 73 + "ref": "#verification" 74 + } 75 + } 76 + }, 77 + "verification": { 78 + "type": "object", 79 + "description": "Latest verification state for a custom domain.", 80 + "required": ["status", "method"], 81 + "properties": { 82 + "status": { 83 + "type": "string", 84 + "enum": ["pending", "verified", "failed"] 85 + }, 86 + "method": { 87 + "type": "string", 88 + "enum": ["txt-did-v1"] 89 + }, 90 + "lastCheckedAt": { 91 + "type": "string", 92 + "format": "datetime" 93 + }, 94 + "verifiedAt": { 95 + "type": "string", 96 + "format": "datetime" 97 + }, 98 + "lastError": { 99 + "type": "string", 100 + "maxLength": 1000 101 + } 102 + } 103 + } 104 + } 105 + }
+18 -6
packages/@wispplace/lexicons/README.md
··· 1 - # @wisp/lexicons 1 + # @wispplace/lexicons 2 2 3 3 Shared AT Protocol lexicon definitions and generated TypeScript types for the wisp.place project. 4 4 5 5 ## Contents 6 6 7 7 - `/lexicons` - Source lexicon JSON definitions 8 - - `/src` - Generated TypeScript types and validation functions 8 + - `/src/types` - Generated TypeScript types and validators (`@atproto/lex-cli`) 9 + - `/src/atcute` - Generated atcute bindings (`@atcute/lex-cli`) 9 10 10 11 ## Usage 11 12 12 13 ```typescript 13 - import { ids, lexicons } from '@wisp/lexicons'; 14 - import type { PlaceWispFs } from '@wisp/lexicons/types/place/wisp/fs'; 14 + import { ids, lexicons } from '@wispplace/lexicons'; 15 + import type { PlaceWispFs } from '@wispplace/lexicons/types/place/wisp/fs'; 16 + import { PlaceWispV2DomainClaim } from '@wispplace/lexicons/atcute'; 15 17 ``` 16 18 17 19 ## Code Generation ··· 19 21 To regenerate types from lexicon definitions: 20 22 21 23 ```bash 22 - npm run codegen 24 + bun run codegen 25 + bun run codegen:atcute 23 26 ``` 24 27 25 - This uses `@atproto/lex-cli` to generate TypeScript types from the JSON schemas in `/lexicons`. 28 + From monorepo root you can run both with: 29 + 30 + ```bash 31 + bun run scripts/codegen.sh 32 + ``` 33 + 34 + Generation uses: 35 + 36 + - `@atproto/lex-cli` for `src/types` 37 + - `@atcute/lex-cli` for `src/atcute`
+6
packages/@wispplace/lexicons/lex.atcute.config.js
··· 1 + import { defineLexiconConfig } from '@atcute/lex-cli'; 2 + 3 + export default defineLexiconConfig({ 4 + files: ['../../../lexicons/*-v2.json'], 5 + outdir: 'src/atcute/lexicons', 6 + });
+20 -1
packages/@wispplace/lexicons/package.json
··· 22 22 "types": "./src/types/place/wisp/subfs.ts", 23 23 "default": "./src/types/place/wisp/subfs.ts" 24 24 }, 25 + "./types/place/wisp/v2/domains": { 26 + "types": "./src/types/place/wisp/v2/domains.ts", 27 + "default": "./src/types/place/wisp/v2/domains.ts" 28 + }, 29 + "./types/place/wisp/v2/domain/claim": { 30 + "types": "./src/types/place/wisp/v2/domain/claim.ts", 31 + "default": "./src/types/place/wisp/v2/domain/claim.ts" 32 + }, 33 + "./types/place/wisp/v2/domain/getStatus": { 34 + "types": "./src/types/place/wisp/v2/domain/getStatus.ts", 35 + "default": "./src/types/place/wisp/v2/domain/getStatus.ts" 36 + }, 37 + "./atcute": { 38 + "types": "./src/atcute/index.ts", 39 + "default": "./src/atcute/index.ts" 40 + }, 25 41 "./lexicons": { 26 42 "types": "./src/lexicons.ts", 27 43 "default": "./src/lexicons.ts" ··· 32 48 } 33 49 }, 34 50 "scripts": { 35 - "codegen": "lex gen-server ./src ../../../lexicons/*.json" 51 + "codegen": "lex gen-server ./src ../../../lexicons/*.json", 52 + "codegen:atcute": "lex-cli generate -c lex.atcute.config.js" 36 53 }, 37 54 "dependencies": { 55 + "@atcute/lexicons": "^1.2.9", 38 56 "@atproto/lexicon": "^0.5.1", 39 57 "@atproto/xrpc-server": "^0.9.5" 40 58 }, 41 59 "devDependencies": { 60 + "@atcute/lex-cli": "^2.5.3", 42 61 "@atproto/lex-cli": "^0.9.5", 43 62 "multiformats": "^13.4.1" 44 63 }
+1
packages/@wispplace/lexicons/src/atcute/index.ts
··· 1 + export * from './lexicons/index';
+3
packages/@wispplace/lexicons/src/atcute/lexicons/index.ts
··· 1 + export * as PlaceWispV2DomainClaim from "./types/place/wisp/v2/domain/claim.js"; 2 + export * as PlaceWispV2DomainGetStatus from "./types/place/wisp/v2/domain/getStatus.js"; 3 + export * as PlaceWispV2Domains from "./types/place/wisp/v2/domains.js";
+89
packages/@wispplace/lexicons/src/atcute/lexicons/types/place/wisp/v2/domain/claim.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.procedure("place.wisp.v2.domain.claim", { 6 + params: null, 7 + input: { 8 + type: "lex", 9 + schema: /*#__PURE__*/ v.object({ 10 + /** 11 + * Domain to claim (wisp subdomain FQDN or custom domain FQDN). 12 + * @minLength 3 13 + * @maxLength 253 14 + */ 15 + domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 16 + /*#__PURE__*/ v.stringLength(3, 253), 17 + ]), 18 + /** 19 + * Optional place.wisp.fs rkey to map immediately after claim. 20 + */ 21 + siteRkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.recordKeyString()), 22 + }), 23 + }, 24 + output: { 25 + type: "lex", 26 + schema: /*#__PURE__*/ v.object({ 27 + /** 28 + * Identifier used to construct DNS challenge targets for custom domains. 29 + * @minLength 8 30 + * @maxLength 64 31 + */ 32 + challengeId: /*#__PURE__*/ v.optional( 33 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 34 + /*#__PURE__*/ v.stringLength(8, 64), 35 + ]), 36 + ), 37 + /** 38 + * Advisory CNAME target (custom domains). 39 + * @minLength 3 40 + * @maxLength 253 41 + */ 42 + cnameTarget: /*#__PURE__*/ v.optional( 43 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 44 + /*#__PURE__*/ v.stringLength(3, 253), 45 + ]), 46 + ), 47 + domain: /*#__PURE__*/ v.string(), 48 + kind: /*#__PURE__*/ v.optional( 49 + /*#__PURE__*/ v.literalEnum(["custom", "wisp"]), 50 + ), 51 + siteRkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.recordKeyString()), 52 + status: /*#__PURE__*/ v.literalEnum([ 53 + "alreadyClaimed", 54 + "pendingVerification", 55 + "verified", 56 + ]), 57 + /** 58 + * TXT hostname to set for ownership proof (custom domains). 59 + * @minLength 3 60 + * @maxLength 253 61 + */ 62 + txtName: /*#__PURE__*/ v.optional( 63 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 64 + /*#__PURE__*/ v.stringLength(3, 253), 65 + ]), 66 + ), 67 + /** 68 + * TXT value to set for ownership proof (custom domains). 69 + */ 70 + txtValue: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.didString()), 71 + }), 72 + }, 73 + }); 74 + 75 + type main$schematype = typeof _mainSchema; 76 + 77 + export interface mainSchema extends main$schematype {} 78 + 79 + export const mainSchema = _mainSchema as mainSchema; 80 + 81 + export interface $params {} 82 + export interface $input extends v.InferXRPCBodyInput<mainSchema["input"]> {} 83 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 84 + 85 + declare module "@atcute/lexicons/ambient" { 86 + interface XRPCProcedures { 87 + "place.wisp.v2.domain.claim": mainSchema; 88 + } 89 + }
+57
packages/@wispplace/lexicons/src/atcute/lexicons/types/place/wisp/v2/domain/getStatus.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.query("place.wisp.v2.domain.getStatus", { 6 + params: /*#__PURE__*/ v.object({ 7 + /** 8 + * Domain to inspect (FQDN, lowercase preferred). 9 + * @minLength 3 10 + * @maxLength 253 11 + */ 12 + domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 13 + /*#__PURE__*/ v.stringLength(3, 253), 14 + ]), 15 + }), 16 + output: { 17 + type: "lex", 18 + schema: /*#__PURE__*/ v.object({ 19 + domain: /*#__PURE__*/ v.string(), 20 + kind: /*#__PURE__*/ v.optional( 21 + /*#__PURE__*/ v.literalEnum(["custom", "wisp"]), 22 + ), 23 + lastCheckedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 24 + /** 25 + * @maxLength 1000 26 + */ 27 + lastError: /*#__PURE__*/ v.optional( 28 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 29 + /*#__PURE__*/ v.stringLength(0, 1000), 30 + ]), 31 + ), 32 + siteRkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.recordKeyString()), 33 + status: /*#__PURE__*/ v.literalEnum([ 34 + "alreadyClaimed", 35 + "pendingVerification", 36 + "unclaimed", 37 + "verified", 38 + ]), 39 + verified: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 40 + }), 41 + }, 42 + }); 43 + 44 + type main$schematype = typeof _mainSchema; 45 + 46 + export interface mainSchema extends main$schematype {} 47 + 48 + export const mainSchema = _mainSchema as mainSchema; 49 + 50 + export interface $params extends v.InferInput<mainSchema["params"]> {} 51 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 52 + 53 + declare module "@atcute/lexicons/ambient" { 54 + interface XRPCQueries { 55 + "place.wisp.v2.domain.getStatus": mainSchema; 56 + } 57 + }
+110
packages/@wispplace/lexicons/src/atcute/lexicons/types/place/wisp/v2/domains.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _customRegistrationSchema = /*#__PURE__*/ v.object({ 6 + $type: /*#__PURE__*/ v.optional( 7 + /*#__PURE__*/ v.literal("place.wisp.v2.domains#customRegistration"), 8 + ), 9 + /** 10 + * Challenge identifier used to derive DNS setup instructions. 11 + * @minLength 8 12 + * @maxLength 64 13 + */ 14 + challengeId: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 15 + /*#__PURE__*/ v.stringLength(8, 64), 16 + ]), 17 + kind: /*#__PURE__*/ v.literal("custom"), 18 + get verification() { 19 + return verificationSchema; 20 + }, 21 + }); 22 + const _mainSchema = /*#__PURE__*/ v.record( 23 + /*#__PURE__*/ v.string(), 24 + /*#__PURE__*/ v.object({ 25 + $type: /*#__PURE__*/ v.literal("place.wisp.v2.domains"), 26 + createdAt: /*#__PURE__*/ v.datetimeString(), 27 + /** 28 + * Lowercase FQDN for this registration (for example, alice.wisp.place or example.com). 29 + * @minLength 3 30 + * @maxLength 253 31 + */ 32 + domain: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 33 + /*#__PURE__*/ v.stringLength(3, 253), 34 + ]), 35 + get registration() { 36 + return /*#__PURE__*/ v.variant([ 37 + customRegistrationSchema, 38 + wispRegistrationSchema, 39 + ]); 40 + }, 41 + /** 42 + * Optional place.wisp.fs record key currently mapped to this domain. 43 + */ 44 + siteRkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.recordKeyString()), 45 + updatedAt: /*#__PURE__*/ v.datetimeString(), 46 + }), 47 + ); 48 + const _verificationSchema = /*#__PURE__*/ v.object({ 49 + $type: /*#__PURE__*/ v.optional( 50 + /*#__PURE__*/ v.literal("place.wisp.v2.domains#verification"), 51 + ), 52 + lastCheckedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 53 + /** 54 + * @maxLength 1000 55 + */ 56 + lastError: /*#__PURE__*/ v.optional( 57 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 58 + /*#__PURE__*/ v.stringLength(0, 1000), 59 + ]), 60 + ), 61 + method: /*#__PURE__*/ v.literalEnum(["txt-did-v1"]), 62 + status: /*#__PURE__*/ v.literalEnum(["failed", "pending", "verified"]), 63 + verifiedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 64 + }); 65 + const _wispRegistrationSchema = /*#__PURE__*/ v.object({ 66 + $type: /*#__PURE__*/ v.optional( 67 + /*#__PURE__*/ v.literal("place.wisp.v2.domains#wispRegistration"), 68 + ), 69 + /** 70 + * Subdomain label only (for example, alice). 71 + * @minLength 3 72 + * @maxLength 63 73 + */ 74 + handle: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 75 + /*#__PURE__*/ v.stringLength(3, 63), 76 + ]), 77 + kind: /*#__PURE__*/ v.literal("wisp"), 78 + }); 79 + 80 + type customRegistration$schematype = typeof _customRegistrationSchema; 81 + type main$schematype = typeof _mainSchema; 82 + type verification$schematype = typeof _verificationSchema; 83 + type wispRegistration$schematype = typeof _wispRegistrationSchema; 84 + 85 + export interface customRegistrationSchema extends customRegistration$schematype {} 86 + export interface mainSchema extends main$schematype {} 87 + export interface verificationSchema extends verification$schematype {} 88 + export interface wispRegistrationSchema extends wispRegistration$schematype {} 89 + 90 + export const customRegistrationSchema = 91 + _customRegistrationSchema as customRegistrationSchema; 92 + export const mainSchema = _mainSchema as mainSchema; 93 + export const verificationSchema = _verificationSchema as verificationSchema; 94 + export const wispRegistrationSchema = 95 + _wispRegistrationSchema as wispRegistrationSchema; 96 + 97 + export interface CustomRegistration extends v.InferInput< 98 + typeof customRegistrationSchema 99 + > {} 100 + export interface Main extends v.InferInput<typeof mainSchema> {} 101 + export interface Verification extends v.InferInput<typeof verificationSchema> {} 102 + export interface WispRegistration extends v.InferInput< 103 + typeof wispRegistrationSchema 104 + > {} 105 + 106 + declare module "@atcute/lexicons/ambient" { 107 + interface Records { 108 + "place.wisp.v2.domains": mainSchema; 109 + } 110 + }
+46
packages/@wispplace/lexicons/src/index.ts
··· 10 10 createServer as createXrpcServer, 11 11 } from '@atproto/xrpc-server' 12 12 import { schemas } from './lexicons.js' 13 + import * as PlaceWispV2DomainClaim from './types/place/wisp/v2/domain/claim.js' 14 + import * as PlaceWispV2DomainGetStatus from './types/place/wisp/v2/domain/getStatus.js' 13 15 14 16 export function createServer(options?: XrpcOptions): Server { 15 17 return new Server(options) ··· 37 39 38 40 export class PlaceWispNS { 39 41 _server: Server 42 + v2: PlaceWispV2NS 40 43 41 44 constructor(server: Server) { 42 45 this._server = server 46 + this.v2 = new PlaceWispV2NS(server) 47 + } 48 + } 49 + 50 + export class PlaceWispV2NS { 51 + _server: Server 52 + domain: PlaceWispV2DomainNS 53 + 54 + constructor(server: Server) { 55 + this._server = server 56 + this.domain = new PlaceWispV2DomainNS(server) 57 + } 58 + } 59 + 60 + export class PlaceWispV2DomainNS { 61 + _server: Server 62 + 63 + constructor(server: Server) { 64 + this._server = server 65 + } 66 + 67 + claim<A extends Auth = void>( 68 + cfg: MethodConfigOrHandler< 69 + A, 70 + PlaceWispV2DomainClaim.QueryParams, 71 + PlaceWispV2DomainClaim.HandlerInput, 72 + PlaceWispV2DomainClaim.HandlerOutput 73 + >, 74 + ) { 75 + const nsid = 'place.wisp.v2.domain.claim' // @ts-ignore 76 + return this._server.xrpc.method(nsid, cfg) 77 + } 78 + 79 + getStatus<A extends Auth = void>( 80 + cfg: MethodConfigOrHandler< 81 + A, 82 + PlaceWispV2DomainGetStatus.QueryParams, 83 + PlaceWispV2DomainGetStatus.HandlerInput, 84 + PlaceWispV2DomainGetStatus.HandlerOutput 85 + >, 86 + ) { 87 + const nsid = 'place.wisp.v2.domain.getStatus' // @ts-ignore 88 + return this._server.xrpc.method(nsid, cfg) 43 89 } 44 90 }
+278
packages/@wispplace/lexicons/src/lexicons.ts
··· 10 10 import { type $Typed, is$typed, maybe$typed } from './util.js' 11 11 12 12 export const schemaDict = { 13 + PlaceWispV2DomainClaim: { 14 + lexicon: 1, 15 + id: 'place.wisp.v2.domain.claim', 16 + defs: { 17 + main: { 18 + type: 'procedure', 19 + description: 20 + 'Claim a domain for the authenticated DID. Returns DNS setup instructions for custom domains.', 21 + input: { 22 + encoding: 'application/json', 23 + schema: { 24 + type: 'object', 25 + required: ['domain'], 26 + properties: { 27 + domain: { 28 + type: 'string', 29 + description: 30 + 'Domain to claim (wisp subdomain FQDN or custom domain FQDN).', 31 + minLength: 3, 32 + maxLength: 253, 33 + }, 34 + siteRkey: { 35 + type: 'string', 36 + format: 'record-key', 37 + description: 38 + 'Optional place.wisp.fs rkey to map immediately after claim.', 39 + }, 40 + }, 41 + }, 42 + }, 43 + output: { 44 + encoding: 'application/json', 45 + schema: { 46 + type: 'object', 47 + required: ['domain', 'status'], 48 + properties: { 49 + domain: { 50 + type: 'string', 51 + }, 52 + kind: { 53 + type: 'string', 54 + enum: ['wisp', 'custom'], 55 + }, 56 + status: { 57 + type: 'string', 58 + enum: ['alreadyClaimed', 'pendingVerification', 'verified'], 59 + }, 60 + challengeId: { 61 + type: 'string', 62 + description: 63 + 'Identifier used to construct DNS challenge targets for custom domains.', 64 + minLength: 8, 65 + maxLength: 64, 66 + }, 67 + txtName: { 68 + type: 'string', 69 + description: 70 + 'TXT hostname to set for ownership proof (custom domains).', 71 + minLength: 3, 72 + maxLength: 253, 73 + }, 74 + txtValue: { 75 + type: 'string', 76 + format: 'did', 77 + description: 78 + 'TXT value to set for ownership proof (custom domains).', 79 + }, 80 + cnameTarget: { 81 + type: 'string', 82 + description: 'Advisory CNAME target (custom domains).', 83 + minLength: 3, 84 + maxLength: 253, 85 + }, 86 + siteRkey: { 87 + type: 'string', 88 + format: 'record-key', 89 + }, 90 + }, 91 + }, 92 + }, 93 + errors: [ 94 + { 95 + name: 'AuthenticationRequired', 96 + }, 97 + { 98 + name: 'InvalidDomain', 99 + }, 100 + { 101 + name: 'AlreadyClaimed', 102 + }, 103 + { 104 + name: 'DomainLimitReached', 105 + }, 106 + { 107 + name: 'RateLimitExceeded', 108 + }, 109 + ], 110 + }, 111 + }, 112 + }, 113 + PlaceWispV2DomainGetStatus: { 114 + lexicon: 1, 115 + id: 'place.wisp.v2.domain.getStatus', 116 + defs: { 117 + main: { 118 + type: 'query', 119 + description: 'Get current claim and verification status for a domain.', 120 + parameters: { 121 + type: 'params', 122 + required: ['domain'], 123 + properties: { 124 + domain: { 125 + type: 'string', 126 + description: 'Domain to inspect (FQDN, lowercase preferred).', 127 + minLength: 3, 128 + maxLength: 253, 129 + }, 130 + }, 131 + }, 132 + output: { 133 + encoding: 'application/json', 134 + schema: { 135 + type: 'object', 136 + required: ['domain', 'status'], 137 + properties: { 138 + domain: { 139 + type: 'string', 140 + }, 141 + status: { 142 + type: 'string', 143 + enum: [ 144 + 'unclaimed', 145 + 'pendingVerification', 146 + 'verified', 147 + 'alreadyClaimed', 148 + ], 149 + }, 150 + kind: { 151 + type: 'string', 152 + enum: ['wisp', 'custom'], 153 + }, 154 + verified: { 155 + type: 'boolean', 156 + }, 157 + lastCheckedAt: { 158 + type: 'string', 159 + format: 'datetime', 160 + }, 161 + lastError: { 162 + type: 'string', 163 + maxLength: 1000, 164 + }, 165 + siteRkey: { 166 + type: 'string', 167 + format: 'record-key', 168 + }, 169 + }, 170 + }, 171 + }, 172 + }, 173 + }, 174 + }, 175 + PlaceWispV2Domains: { 176 + lexicon: 1, 177 + id: 'place.wisp.v2.domains', 178 + defs: { 179 + main: { 180 + type: 'record', 181 + description: 182 + 'Domain registration metadata for wisp.place subdomains and custom domains.', 183 + key: 'any', 184 + record: { 185 + type: 'object', 186 + required: ['domain', 'registration', 'createdAt', 'updatedAt'], 187 + properties: { 188 + domain: { 189 + type: 'string', 190 + description: 191 + 'Lowercase FQDN for this registration (for example, alice.wisp.place or example.com).', 192 + minLength: 3, 193 + maxLength: 253, 194 + }, 195 + registration: { 196 + type: 'union', 197 + refs: [ 198 + 'lex:place.wisp.v2.domains#wispRegistration', 199 + 'lex:place.wisp.v2.domains#customRegistration', 200 + ], 201 + }, 202 + siteRkey: { 203 + type: 'string', 204 + format: 'record-key', 205 + description: 206 + 'Optional place.wisp.fs record key currently mapped to this domain.', 207 + }, 208 + createdAt: { 209 + type: 'string', 210 + format: 'datetime', 211 + }, 212 + updatedAt: { 213 + type: 'string', 214 + format: 'datetime', 215 + }, 216 + }, 217 + }, 218 + }, 219 + wispRegistration: { 220 + type: 'object', 221 + description: 222 + 'Registration for a first-party subdomain under the wisp.place base host.', 223 + required: ['kind', 'handle'], 224 + properties: { 225 + kind: { 226 + type: 'string', 227 + const: 'wisp', 228 + }, 229 + handle: { 230 + type: 'string', 231 + description: 'Subdomain label only (for example, alice).', 232 + minLength: 3, 233 + maxLength: 63, 234 + }, 235 + }, 236 + }, 237 + customRegistration: { 238 + type: 'object', 239 + description: 'Registration metadata for a custom domain.', 240 + required: ['kind', 'challengeId', 'verification'], 241 + properties: { 242 + kind: { 243 + type: 'string', 244 + const: 'custom', 245 + }, 246 + challengeId: { 247 + type: 'string', 248 + description: 249 + 'Challenge identifier used to derive DNS setup instructions.', 250 + minLength: 8, 251 + maxLength: 64, 252 + }, 253 + verification: { 254 + type: 'ref', 255 + ref: 'lex:place.wisp.v2.domains#verification', 256 + }, 257 + }, 258 + }, 259 + verification: { 260 + type: 'object', 261 + description: 'Latest verification state for a custom domain.', 262 + required: ['status', 'method'], 263 + properties: { 264 + status: { 265 + type: 'string', 266 + enum: ['pending', 'verified', 'failed'], 267 + }, 268 + method: { 269 + type: 'string', 270 + enum: ['txt-did-v1'], 271 + }, 272 + lastCheckedAt: { 273 + type: 'string', 274 + format: 'datetime', 275 + }, 276 + verifiedAt: { 277 + type: 'string', 278 + format: 'datetime', 279 + }, 280 + lastError: { 281 + type: 'string', 282 + maxLength: 1000, 283 + }, 284 + }, 285 + }, 286 + }, 287 + }, 13 288 PlaceWispFs: { 14 289 lexicon: 1, 15 290 id: 'place.wisp.fs', ··· 358 633 } 359 634 360 635 export const ids = { 636 + PlaceWispV2DomainClaim: 'place.wisp.v2.domain.claim', 637 + PlaceWispV2DomainGetStatus: 'place.wisp.v2.domain.getStatus', 638 + PlaceWispV2Domains: 'place.wisp.v2.domains', 361 639 PlaceWispFs: 'place.wisp.fs', 362 640 PlaceWispSettings: 'place.wisp.settings', 363 641 PlaceWispSubfs: 'place.wisp.subfs',
+63
packages/@wispplace/lexicons/src/types/place/wisp/v2/domain/claim.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'place.wisp.v2.domain.claim' 16 + 17 + export type QueryParams = {} 18 + 19 + export interface InputSchema { 20 + /** Domain to claim (wisp subdomain FQDN or custom domain FQDN). */ 21 + domain: string 22 + /** Optional place.wisp.fs rkey to map immediately after claim. */ 23 + siteRkey?: string 24 + } 25 + 26 + export interface OutputSchema { 27 + domain: string 28 + kind?: 'wisp' | 'custom' 29 + status: 'alreadyClaimed' | 'pendingVerification' | 'verified' 30 + /** Identifier used to construct DNS challenge targets for custom domains. */ 31 + challengeId?: string 32 + /** TXT hostname to set for ownership proof (custom domains). */ 33 + txtName?: string 34 + /** TXT value to set for ownership proof (custom domains). */ 35 + txtValue?: string 36 + /** Advisory CNAME target (custom domains). */ 37 + cnameTarget?: string 38 + siteRkey?: string 39 + } 40 + 41 + export interface HandlerInput { 42 + encoding: 'application/json' 43 + body: InputSchema 44 + } 45 + 46 + export interface HandlerSuccess { 47 + encoding: 'application/json' 48 + body: OutputSchema 49 + headers?: { [key: string]: string } 50 + } 51 + 52 + export interface HandlerError { 53 + status: number 54 + message?: string 55 + error?: 56 + | 'AuthenticationRequired' 57 + | 'InvalidDomain' 58 + | 'AlreadyClaimed' 59 + | 'DomainLimitReached' 60 + | 'RateLimitExceeded' 61 + } 62 + 63 + export type HandlerOutput = HandlerError | HandlerSuccess
+46
packages/@wispplace/lexicons/src/types/place/wisp/v2/domain/getStatus.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'place.wisp.v2.domain.getStatus' 16 + 17 + export type QueryParams = { 18 + /** Domain to inspect (FQDN, lowercase preferred). */ 19 + domain: string 20 + } 21 + export type InputSchema = undefined 22 + 23 + export interface OutputSchema { 24 + domain: string 25 + status: 'unclaimed' | 'pendingVerification' | 'verified' | 'alreadyClaimed' 26 + kind?: 'wisp' | 'custom' 27 + verified?: boolean 28 + lastCheckedAt?: string 29 + lastError?: string 30 + siteRkey?: string 31 + } 32 + 33 + export type HandlerInput = void 34 + 35 + export interface HandlerSuccess { 36 + encoding: 'application/json' 37 + body: OutputSchema 38 + headers?: { [key: string]: string } 39 + } 40 + 41 + export interface HandlerError { 42 + status: number 43 + message?: string 44 + } 45 + 46 + export type HandlerOutput = HandlerError | HandlerSuccess
+103
packages/@wispplace/lexicons/src/types/place/wisp/v2/domains.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'place.wisp.v2.domains' 16 + 17 + export interface Main { 18 + $type: 'place.wisp.v2.domains' 19 + /** Lowercase FQDN for this registration (for example, alice.wisp.place or example.com). */ 20 + domain: string 21 + registration: 22 + | $Typed<WispRegistration> 23 + | $Typed<CustomRegistration> 24 + | { $type: string } 25 + /** Optional place.wisp.fs record key currently mapped to this domain. */ 26 + siteRkey?: string 27 + createdAt: string 28 + updatedAt: string 29 + [k: string]: unknown 30 + } 31 + 32 + const hashMain = 'main' 33 + 34 + export function isMain<V>(v: V) { 35 + return is$typed(v, id, hashMain) 36 + } 37 + 38 + export function validateMain<V>(v: V) { 39 + return validate<Main & V>(v, id, hashMain, true) 40 + } 41 + 42 + export { 43 + type Main as Record, 44 + isMain as isRecord, 45 + validateMain as validateRecord, 46 + } 47 + 48 + /** Registration for a first-party subdomain under the wisp.place base host. */ 49 + export interface WispRegistration { 50 + $type?: 'place.wisp.v2.domains#wispRegistration' 51 + kind: 'wisp' 52 + /** Subdomain label only (for example, alice). */ 53 + handle: string 54 + } 55 + 56 + const hashWispRegistration = 'wispRegistration' 57 + 58 + export function isWispRegistration<V>(v: V) { 59 + return is$typed(v, id, hashWispRegistration) 60 + } 61 + 62 + export function validateWispRegistration<V>(v: V) { 63 + return validate<WispRegistration & V>(v, id, hashWispRegistration) 64 + } 65 + 66 + /** Registration metadata for a custom domain. */ 67 + export interface CustomRegistration { 68 + $type?: 'place.wisp.v2.domains#customRegistration' 69 + kind: 'custom' 70 + /** Challenge identifier used to derive DNS setup instructions. */ 71 + challengeId: string 72 + verification: Verification 73 + } 74 + 75 + const hashCustomRegistration = 'customRegistration' 76 + 77 + export function isCustomRegistration<V>(v: V) { 78 + return is$typed(v, id, hashCustomRegistration) 79 + } 80 + 81 + export function validateCustomRegistration<V>(v: V) { 82 + return validate<CustomRegistration & V>(v, id, hashCustomRegistration) 83 + } 84 + 85 + /** Latest verification state for a custom domain. */ 86 + export interface Verification { 87 + $type?: 'place.wisp.v2.domains#verification' 88 + status: 'pending' | 'verified' | 'failed' 89 + method: 'txt-did-v1' 90 + lastCheckedAt?: string 91 + verifiedAt?: string 92 + lastError?: string 93 + } 94 + 95 + const hashVerification = 'verification' 96 + 97 + export function isVerification<V>(v: V) { 98 + return is$typed(v, id, hashVerification) 99 + } 100 + 101 + export function validateVerification<V>(v: V) { 102 + return validate<Verification & V>(v, id, hashVerification) 103 + }
+3 -1
packages/@wispplace/lexicons/tsconfig.json
··· 4 4 "outDir": "./dist", 5 5 "rootDir": "./src", 6 6 "declaration": true, 7 - "declarationMap": true 7 + "declarationMap": true, 8 + "allowImportingTsExtensions": true, 9 + "noEmit": true 8 10 }, 9 11 "include": ["src/**/*"], 10 12 "exclude": ["node_modules", "dist"]
+5 -2
scripts/codegen.sh
··· 11 11 fi 12 12 13 13 echo "=== Generating TypeScript lexicons ===" 14 - cd "$ROOT_DIR/packages/@wisp/lexicons" 15 - eval "$AUTO_ACCEPT npm run codegen" 14 + cd "$ROOT_DIR/packages/@wispplace/lexicons" 15 + eval "$AUTO_ACCEPT bun run codegen" 16 + 17 + echo "=== Generating atcute lexicons ===" 18 + eval "$AUTO_ACCEPT bun run codegen:atcute" 16 19 17 20 echo "=== Done ==="