···11# Contributing
2233-Hi, thanks for showing interest in our project, we appreciate you wanting to be
33+Hi, thanks for showing interest in our project, we appreciate you wanting to be
44a part of it!
5566> ###### Do you accept external contributions?
77-> Yes, we welcome external contributions! The only request we have is that you
88-> check with us before working on a large feature, as we'd prefer to avoid duplicating effort.
77+>
88+> Yes, we welcome external contributions! The only request we have is that you
99+> check with us before working on a large feature, as we'd prefer to avoid duplicating effort.
910>
1010-> You can do this by either creating an issue that we can communicate through, or
1111-> popping into the Discord channel and asking in the #development channel.
1111+> You can do this by either creating an issue that we can communicate through, or
1212+> popping into the Discord channel and asking in the #development channel.
1213> Whatever works best for you!
1314>
1415> Other than that, feel free to contribute however you'd like!
···3839turbo dev --filter=<service_path>
3940```
40414141-Open http://localhost:3000/ with your browser to see the home page. You will need
4242-to login with Bluesky to test the posting functionality of the app. Note: if the
4343-redirect back to the app after you login isn't working correctly, you may need to
4242+Open http://localhost:3000/ with your browser to see the home page. You will need
4343+to login with Bluesky to test the posting functionality of the app. Note: if the
4444+redirect back to the app after you login isn't working correctly, you may need to
4445replace the `127.0.0.1` with `localhost`.
45464647Push to your fork and [submit a pull request][pr].
+2
README.md
···11## Getting Started
2233### Prerequisites
44+45- Node (>= v21.0.0)
56- Go
67- Bun
···1213pnpm install && pnpm install -g turbo && cp apps/aqua/.env.example apps/aqua/.env &&
1314pnpm run db:migrate
1415```
1616+1517Running on a Mac may also require adding @libsql/darwin-x64 dependency
16181719## Development
···11-import { ScrollViewStyleReset } from 'expo-router/html';
11+import { ScrollViewStyleReset } from "expo-router/html";
2233// This file is web-only and used to configure the root HTML for every
44// web page during static rendering.
···1010 <head>
1111 <meta charSet="utf-8" />
1212 <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
1313- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
1313+ <meta
1414+ name="viewport"
1515+ content="width=device-width, initial-scale=1, shrink-to-fit=no"
1616+ />
14171518 {/*
1619 Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
+1-1
apps/amethyst/app/+not-found.tsx
···11-import { Link, Stack } from "expo-router";
21import { StyleSheet, View } from "react-native";
22+import { Link, Stack } from "expo-router";
3344import { Text } from "../components/ui/text";
55
+7-6
apps/amethyst/app/_layout.tsx
···11+import { useEffect } from "react";
22+import { useFonts } from "expo-font";
33+import { Stack } from "expo-router";
44+import * as SplashScreen from "expo-splash-screen";
15import FontAwesome from "@expo/vector-icons/FontAwesome";
26import {
37 DarkTheme,
···610 ThemeProvider,
711} from "@react-navigation/native";
812import { PortalHost } from "@rn-primitives/portal";
99-import { useFonts } from "expo-font";
1010-import { Stack } from "expo-router";
1111-import * as SplashScreen from "expo-splash-screen";
1212-import { useEffect } from "react";
1313+1314import "react-native-reanimated";
14151516import { GestureHandlerRootView } from "react-native-gesture-handler";
1616-1717-import { verifyInstallation, useColorScheme } from "nativewind";
1717+import { useColorScheme, verifyInstallation } from "nativewind";
18181919import { GlobalTextClassContext } from "../components/ui/text";
2020+2021import "../global.css";
21222223import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
+7-7
apps/amethyst/app/auth/callback.tsx
···11-import { Link, Stack, router } from "expo-router";
11+import React, { useEffect } from "react";
22import { View } from "react-native";
33+import { Link, router, Stack } from "expo-router";
44+import { useLocalSearchParams } from "expo-router/build/hooks";
35import { Text } from "@/components/ui/text";
44-import React, { useEffect } from "react";
56import { Icon } from "@/lib/icons/iconWithClassName";
66-import { PencilLine } from "lucide-react-native";
77-import { useLocalSearchParams } from "expo-router/build/hooks";
87import { useStore } from "@/stores/mainStore";
88+import { PencilLine } from "lucide-react-native";
991010interface CallbackSearchParams {
1111 iss: string;
···4444 );
4545 }
4646 return (
4747- <View className="flex-1 justify-center items-center gap-5 p-6 bg-background">
4747+ <View className="flex-1 items-center justify-center gap-5 bg-background p-6">
4848 <Stack.Screen
4949 options={{
5050 title: "Processing",
···5252 }}
5353 />
5454 <Icon icon={PencilLine} size={64} />
5555- <Text className="text-3xl font-semibold text-center text-foreground">
5555+ <Text className="text-center text-3xl font-semibold text-foreground">
5656 {status === "loggedIn" ? "Success!" : "Fetching your data..."}
5757 </Text>
5858 <Text className="text-sm text-muted-foreground">
5959 This may take a few seconds {status}
6060 </Text>
6161- <Text className="text-sm text-muted-foreground font-mono bg-muted-foreground/30 py-1 px-2 rounded-full">
6161+ <Text className="rounded-full bg-muted-foreground/30 px-2 py-1 font-mono text-sm text-muted-foreground">
6262 {state}
6363 </Text>
6464 </View>
+4-2
apps/amethyst/app/auth/login.tsx
···33import React, { useState, useEffect, useCallback, useRef } from "react"; // Added useCallback, useRef
44import { Platform, TextInput, View } from "react-native";
55import { SafeAreaView } from "react-native-safe-area-context";
66+import { Link, router, Stack } from "expo-router";
77+import { openAuthSessionAsync } from "expo-web-browser";
68import { Button } from "@/components/ui/button";
79import { Input } from "@/components/ui/input";
810import { Text } from "@/components/ui/text";
···210212 };
211213212214 return (
213213- <SafeAreaView className="flex-1 flex items-center justify-center w-full">
215215+ <SafeAreaView className="flex w-full flex-1 items-center justify-center">
214216 <Stack.Screen
215217 options={{
216218 title: "Sign in",
···222224 <View className="flex items-center">
223225 <Icon icon={AtSign} className="color-bsky" name="at" size={64} />
224226 </View>
225225- <Text className="text-3xl text-center text-foreground">
227227+ <Text className="text-center text-3xl text-foreground">
226228 Sign in with your PDS
227229 </Text>
228230 <View>
+8-10
apps/amethyst/app/auth/logoutModal.tsx
···11+import { Platform, TouchableOpacity, View } from "react-native";
22+import { router } from "expo-router";
13import { StatusBar } from "expo-status-bar";
22-import { Platform, TouchableOpacity } from "react-native";
33-44-import { View } from "react-native";
44+import { Button } from "@/components/ui/button";
55import { Text } from "@/components/ui/text";
66+import { Icon } from "@/lib/icons/iconWithClassName";
67import { useStore } from "@/stores/mainStore";
77-import { Button } from "@/components/ui/button";
88-import { router } from "expo-router";
98import { X } from "lucide-react-native";
1010-import { Icon } from "@/lib/icons/iconWithClassName";
1191210// should probably be a WebModal component or something?
1311export default function ModalScreen() {
···1816 };
1917 return (
2018 <TouchableOpacity
2121- className="flex justify-center items-center bg-muted/60 w-full h-screen backdrop-blur-sm fade-in animate-in"
1919+ className="flex h-screen w-full items-center justify-center bg-muted/60 backdrop-blur-sm animate-in fade-in"
2220 onPress={() => handleGoBack()}
2321 >
2424- <View className="flex-1 relative items-center justify-center gap-2 bg-background w-full max-w-96 max-h-80 shadow-xl rounded-xl">
2222+ <View className="relative max-h-80 w-full max-w-96 flex-1 items-center justify-center gap-2 rounded-xl bg-background shadow-xl">
2523 <Icon
2624 icon={X}
2727- className="top-2 right-2 absolute text-muted-foreground hover:text-foreground"
2525+ className="absolute right-2 top-2 text-muted-foreground hover:text-foreground"
2826 name="x"
2927 />
3028 <Text className="text-4xl">Surprise!</Text>
3129 <Text className="text-xl">You can sign out here!</Text>
3232- <Text className="text-xl -mt-2">but... are you sure?</Text>
3030+ <Text className="-mt-2 text-xl">but... are you sure?</Text>
3331 <Button
3432 onPress={() => {
3533 logOut();
···11+import React from "react";
22+import { Platform } from "react-native";
13import { Link } from "expo-router";
24import * as WebBrowser from "expo-web-browser";
33-import React from "react";
44-import { Platform } from "react-native";
5566export function ExternalLink(
77 props: Omit<React.ComponentProps<typeof Link>, "href"> & { href: string },
···11-import * as React from 'react';
22-import renderer from 'react-test-renderer';
11+import * as React from "react";
22+import renderer from "react-test-renderer";
3344-import { MonoText } from '../StyledText';
44+import { MonoText } from "../StyledText";
5566it(`renders correctly`, () => {
77 const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
···11+import * as React from "react";
12import * as AvatarPrimitive from "@rn-primitives/avatar";
22-import * as React from "react";
33+34import { cn } from "../../lib/utils";
4556const AvatarPrimitiveRoot = AvatarPrimitive.Root;
+2-1
apps/amethyst/components/ui/button.tsx
···11-import { cva, type VariantProps } from "class-variance-authority";
21import * as React from "react";
32import { Pressable } from "react-native";
33+import { cva, type VariantProps } from "class-variance-authority";
44+45import { TextClassContext } from "../../components/ui/text";
56import { cn } from "../../lib/utils";
67
+4-3
apps/amethyst/components/ui/card.tsx
···11-import type { TextRef, ViewRef } from "@rn-primitives/types";
21import * as React from "react";
32import { TextProps, View, ViewProps } from "react-native";
44-import { TextClassContext, Text } from "../../components/ui/text";
33+import type { TextRef, ViewRef } from "@rn-primitives/types";
44+55+import { Text, TextClassContext } from "../../components/ui/text";
56import { cn } from "../../lib/utils";
6778const Card = React.forwardRef<ViewRef, ViewProps>(
···3839 aria-level={3}
3940 ref={ref}
4041 className={cn(
4141- "text-2xl text-card-foreground font-semibold leading-none tracking-tight",
4242+ "text-2xl font-semibold leading-none tracking-tight text-card-foreground",
4243 className,
4344 )}
4445 {...props}
···11+import * as React from "react";
22+import { Text as RNText } from "react-native";
13import * as Slot from "@rn-primitives/slot";
24import type { SlottableTextProps, TextRef } from "@rn-primitives/types";
33-import * as React from "react";
44-import { Text as RNText } from "react-native";
55+56import { cn } from "../../lib/utils";
6778const TextClassContext = React.createContext<string | undefined>(undefined);
···11-import React from 'react';
11+import React from "react";
2233// `useEffect` is not invoked during server rendering, meaning
44// we can use this to determine if we're on the server or not.
···11-const DEFAULT_IMAGE_TEMPLATE = 'https://at.uwu.wang/{did}/{hash}';
11+const DEFAULT_IMAGE_TEMPLATE = "https://at.uwu.wang/{did}/{hash}";
2233export default function getImageCdnLink({
44 did,
···99}): string | undefined {
1010 if (!did || !hash) return undefined;
1111 // if hash is actually a data url return it
1212- if (hash.startsWith('data:')) return hash;
1313- return DEFAULT_IMAGE_TEMPLATE.replace('{did}', did).replace('{hash}', hash);
1212+ if (hash.startsWith("data:")) return hash;
1313+ return DEFAULT_IMAGE_TEMPLATE.replace("{did}", did).replace("{hash}", hash);
1414}
+2-2
apps/amethyst/lib/atp/oauth.tsx
···11+import { Platform } from "react-native";
22+import Constants from "expo-constants";
13import {
24 ClientMetadata,
35 clientMetadataSchema,
46 ReactNativeOAuthClient,
57} from "@aquareum/atproto-oauth-client-react-native";
66-import Constants from "expo-constants";
77-import { Platform } from "react-native";
8899export type AquareumOAuthClient = Omit<
1010 ReactNativeOAuthClient,
+1
apps/amethyst/lib/icons/CalendarDays.tsx
···11import { CalendarDays } from "lucide-react-native";
22+23import { iconWithClassName } from "./iconWithClassName";
3445iconWithClassName(CalendarDays);
+1
apps/amethyst/lib/icons/Info.tsx
···11import { Info } from "lucide-react-native";
22+23import { iconWithClassName } from "./iconWithClassName";
3445iconWithClassName(Info);
+1
apps/amethyst/lib/icons/Moon.tsx
···11import { Moon } from "lucide-react-native";
22+23import { iconWithClassName } from "./iconWithClassName";
3445iconWithClassName(Moon);
+1
apps/amethyst/lib/icons/Sun.tsx
···11import { Sun } from "lucide-react-native";
22+23import { iconWithClassName } from "./iconWithClassName";
3445iconWithClassName(Sun);
+7-3
apps/amethyst/lib/icons/iconWithClassName.tsx
···11+import React from "react";
22+import type { FontAwesome6 } from "@expo/vector-icons";
13import type { LucideIcon } from "lucide-react-native";
22-import type { FontAwesome6 } from "@expo/vector-icons";
34import { cssInterop } from "nativewind";
55+66+import {
77+ GlobalTextClassContext,
88+ TextClassContext,
99+} from "../../components/ui/text";
410import { cn } from "../utils";
55-import { GlobalTextClassContext, TextClassContext } from "../../components/ui/text";
66-import React from "react";
711812/// This type is used to support multiple icon libraries.
913type SupportedIcons = LucideIcon | typeof FontAwesome6;
-1
apps/amethyst/lib/oldStamp.tsx
···7272 queryParts.push(`release:"${searchParams.release}"`);
7373 }
74747575-7675 const query = queryParts.join(" AND ");
77767877 const res = await fetch(
+1-1
apps/amethyst/nativewind-env.d.ts
···11/// <reference types="nativewind/types" />
2233-// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.33+// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
···11-import { TealContext } from '@/ctx';
22-import { db, profiles } from '@teal/db';
33-import { eq } from 'drizzle-orm';
44-import { OutputSchema } from '@teal/lexicons/src/types/fm/teal/alpha/actor/getProfile';
11+import { TealContext } from "@/ctx";
22+import { eq } from "drizzle-orm";
33+44+import { db, profiles } from "@teal/db";
55+import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/actor/getProfile";
5667export default async function getProfile(c: TealContext) {
78 const params = c.req.query();
89 if (!params.actor) {
99- throw new Error('actor is required');
1010+ throw new Error("actor is required");
1011 }
11121213 // Assuming 'user' can be either a DID or a handle. We'll try to resolve
···3031 }
31323233 if (!profile) {
3333- throw new Error('Profile not found');
3434+ throw new Error("Profile not found");
3435 }
35363637 profile = profile[0];
+13-12
apps/aqua/src/xrpc/actor/searchActors.ts
···11-import { TealContext } from '@/ctx';
22-import { db, profiles } from '@teal/db';
33-import { like, or, sql, lt, gt, and } from 'drizzle-orm';
44-import { OutputSchema } from '@teal/lexicons/src/types/fm/teal/alpha/actor/searchActors';
11+import { TealContext } from "@/ctx";
22+import { and, gt, like, lt, or, sql } from "drizzle-orm";
33+44+import { db, profiles } from "@teal/db";
55+import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/actor/searchActors";
5667export default async function searchActors(c: TealContext) {
78 const params = c.req.query();
···10111112 if (!params.q) {
1213 c.status(400);
1313- c.error = new Error('q is required');
1414+ c.error = new Error("q is required");
1415 return;
1516 }
1617···40414142 if (params.cursor) {
4243 // Decode the cursor
4343- const [createdAtStr, didStr] = Buffer.from(params.cursor, 'base64')
4444- .toString('utf-8')
4545- .split(':');
4444+ const [createdAtStr, didStr] = Buffer.from(params.cursor, "base64")
4545+ .toString("utf-8")
4646+ .split(":");
46474748 const createdAt = new Date(createdAtStr);
4849···7475 if (results.length > limit) {
7576 const lastResult = results[limit - 1]; // Get the *limit*-th, not limit+1-th
7677 nextCursor = Buffer.from(
7777- `${lastResult.createdAt?.toISOString() || ''}:${lastResult.did}`,
7878- ).toString('base64');
7878+ `${lastResult.createdAt?.toISOString() || ""}:${lastResult.did}`,
7979+ ).toString("base64");
7980 results.pop(); // Remove the extra record we fetched
8081 }
8182 const res: OutputSchema = {
···91929293 return res;
9394 } catch (error) {
9494- console.error('Database error:', error);
9595+ console.error("Database error:", error);
9596 c.status(500);
9696- throw new Error('Internal server error');
9797+ throw new Error("Internal server error");
9798 }
9899}
+3-2
apps/aqua/src/xrpc/feed/getPlay.ts
···11import { TealContext } from "@/ctx";
22-import { db, plays, playToArtists, artists } from "@teal/db";
33-import { eq, and, lt, desc, sql } from "drizzle-orm";
22+import { and, desc, eq, lt, sql } from "drizzle-orm";
33+44+import { artists, db, plays, playToArtists } from "@teal/db";
45import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed";
5667export default async function getActorFeed(c: TealContext) {
···11+import path from "node:path";
22+import process from "node:process";
13import { drizzle } from "drizzle-orm/postgres-js";
24import postgres from "postgres";
55+36import * as schema from "./schema";
44-import process from "node:process";
55-import path from "node:path";
6778/// Trim a password from a db connection url
89function withoutPassword(url: string) {
···11/**
22 * GENERATED CODE - DO NOT MODIFY
33 */
44-import { ValidationResult, BlobRef } from '@atproto/lexicon'
55-import { lexicons } from '../../../../../lexicons'
66-import { isObj, hasProp } from '../../../../../util'
77-import { CID } from 'multiformats/cid'
44+import { BlobRef, ValidationResult } from "@atproto/lexicon";
55+import { CID } from "multiformats/cid";
66+77+import { lexicons } from "../../../../../lexicons";
88+import { hasProp, isObj } from "../../../../../util";
89910export interface PlayView {
1011 /** The name of the track */
1111- trackName: string
1212+ trackName: string;
1213 /** The Musicbrainz ID of the track */
1313- trackMbId?: string
1414+ trackMbId?: string;
1415 /** The Musicbrainz recording ID of the track */
1515- recordingMbId?: string
1616+ recordingMbId?: string;
1617 /** The length of the track in seconds */
1718 duration?: number
1819 /** Array of artists in order of original appearance. */
1920 artists: Artist[]
2021 /** The name of the release/album */
2121- releaseName?: string
2222+ releaseName?: string;
2223 /** The Musicbrainz release ID */
2323- releaseMbId?: string
2424+ releaseMbId?: string;
2425 /** The ISRC code associated with the recording */
2525- isrc?: string
2626+ isrc?: string;
2627 /** The URL associated with this track */
2727- originUrl?: string
2828+ originUrl?: string;
2829 /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. Defaults to 'local' if not provided. */
2929- musicServiceBaseDomain?: string
3030+ musicServiceBaseDomain?: string;
3031 /** A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b (Linux; Android 13; SM-A715F). Defaults to 'manual/unknown' if not provided. */
3131- submissionClientAgent?: string
3232+ submissionClientAgent?: string;
3233 /** The unix timestamp of when the track was played */
3333- playedTime?: string
3434- [k: string]: unknown
3434+ playedTime?: string;
3535+ [k: string]: unknown;
3536}
36373738export function isPlayView(v: unknown): v is PlayView {
3839 return (
3940 isObj(v) &&
4040- hasProp(v, '$type') &&
4141- v.$type === 'fm.teal.alpha.feed.defs#playView'
4242- )
4141+ hasProp(v, "$type") &&
4242+ v.$type === "fm.teal.alpha.feed.defs#playView"
4343+ );
4344}
44454546export function validatePlayView(v: unknown): ValidationResult {
4646- return lexicons.validate('fm.teal.alpha.feed.defs#playView', v)
4747+ return lexicons.validate("fm.teal.alpha.feed.defs#playView", v);
4748}
48494950export interface Artist {
···991010export interface Record {
1111 /** The name of the track */
1212- trackName: string
1212+ trackName: string;
1313 /** The Musicbrainz ID of the track */
1414- trackMbId?: string
1414+ trackMbId?: string;
1515 /** The Musicbrainz recording ID of the track */
1616- recordingMbId?: string
1616+ recordingMbId?: string;
1717 /** The length of the track in seconds */
1818 duration?: number
1919 /** Array of artist names in order of original appearance. Prefer using 'artists'. */
···2323 /** Array of artists in order of original appearance. */
2424 artists?: FmTealAlphaFeedDefs.Artist[]
2525 /** The name of the release/album */
2626- releaseName?: string
2626+ releaseName?: string;
2727 /** The Musicbrainz release ID */
2828- releaseMbId?: string
2828+ releaseMbId?: string;
2929 /** The ISRC code associated with the recording */
3030- isrc?: string
3030+ isrc?: string;
3131 /** The URL associated with this track */
3232- originUrl?: string
3232+ originUrl?: string;
3333 /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. Defaults to 'local' if unavailable or not provided. */
3434- musicServiceBaseDomain?: string
3434+ musicServiceBaseDomain?: string;
3535 /** A metadata string specifying the user agent where the format is `<app-identifier>/<version> (<kernel/OS-base>; <platform/OS-version>; <device-model>)`. If string is provided, only `app-identifier` and `version` are required. `app-identifier` is recommended to be in reverse dns format. Defaults to 'manual/unknown' if unavailable or not provided. */
3636- submissionClientAgent?: string
3636+ submissionClientAgent?: string;
3737 /** The unix timestamp of when the track was played */
3838- playedTime?: string
3939- [k: string]: unknown
3838+ playedTime?: string;
3939+ [k: string]: unknown;
4040}
41414242export function isRecord(v: unknown): v is Record {
4343 return (
4444 isObj(v) &&
4545- hasProp(v, '$type') &&
4646- (v.$type === 'fm.teal.alpha.feed.play#main' ||
4747- v.$type === 'fm.teal.alpha.feed.play')
4848- )
4545+ hasProp(v, "$type") &&
4646+ (v.$type === "fm.teal.alpha.feed.play#main" ||
4747+ v.$type === "fm.teal.alpha.feed.play")
4848+ );
4949}
50505151export function validateRecord(v: unknown): ValidationResult {
5252- return lexicons.validate('fm.teal.alpha.feed.play#main', v)
5252+ return lexicons.validate("fm.teal.alpha.feed.play#main", v);
5353}
···11/**
22 * GENERATED CODE - DO NOT MODIFY
33 */
44-import { ValidationResult, BlobRef } from '@atproto/lexicon'
55-import { lexicons } from '../../../lexicons'
66-import { isObj, hasProp } from '../../../util'
77-import { CID } from 'multiformats/cid'
44+import { BlobRef, ValidationResult } from "@atproto/lexicon";
55+import { CID } from "multiformats/cid";
66+77+import { lexicons } from "../../../lexicons";
88+import { hasProp, isObj } from "../../../util";
89910export interface Record {
1010- status: string
1111- createdAt: string
1212- [k: string]: unknown
1111+ status: string;
1212+ createdAt: string;
1313+ [k: string]: unknown;
1314}
14151516export function isRecord(v: unknown): v is Record {
1617 return (
1718 isObj(v) &&
1818- hasProp(v, '$type') &&
1919- (v.$type === 'xyz.statusphere.status#main' ||
2020- v.$type === 'xyz.statusphere.status')
2121- )
1919+ hasProp(v, "$type") &&
2020+ (v.$type === "xyz.statusphere.status#main" ||
2121+ v.$type === "xyz.statusphere.status")
2222+ );
2223}
23242425export function validateRecord(v: unknown): ValidationResult {
2525- return lexicons.validate('xyz.statusphere.status#main', v)
2626+ return lexicons.validate("xyz.statusphere.status#main", v);
2627}
+2-2
packages/lexicons/src/util.ts
···22 * GENERATED CODE - DO NOT MODIFY
33 */
44export function isObj(v: unknown): v is Record<string, unknown> {
55- return typeof v === 'object' && v !== null
55+ return typeof v === "object" && v !== null;
66}
7788export function hasProp<K extends PropertyKey>(
99 data: object,
1010 prop: K,
1111): data is Record<K, unknown> {
1212- return prop in data
1212+ return prop in data;
1313}