a a vibe-coded abomination experiment of a fragrance review platform built on the atmosphere. drydown.social

Add Lexicon code generation command to package scripts

+943 -1
+203
package-lock.json
··· 14 14 "preact": "^10.27.2" 15 15 }, 16 16 "devDependencies": { 17 + "@atproto/lex-cli": "^0.9.8", 17 18 "@preact/preset-vite": "^2.10.2", 18 19 "@types/node": "^24.10.1", 19 20 "typescript": "~5.9.3", ··· 158 159 "@atproto/jwk": "0.6.0", 159 160 "@atproto/jwk-jose": "0.1.11", 160 161 "zod": "^3.23.8" 162 + } 163 + }, 164 + "node_modules/@atproto/lex-cli": { 165 + "version": "0.9.8", 166 + "resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.8.tgz", 167 + "integrity": "sha512-0ebVyp12i3S8oE77+BxahbTmyrXcqeC9GTx2HGa/PA9KjnThapkGkgVQjIWw74DNQprzbg9EkiQsaKU2xFYhmA==", 168 + "dev": true, 169 + "license": "MIT", 170 + "dependencies": { 171 + "@atproto/lexicon": "^0.6.0", 172 + "@atproto/syntax": "^0.4.2", 173 + "chalk": "^4.1.2", 174 + "commander": "^9.4.0", 175 + "prettier": "^3.2.5", 176 + "ts-morph": "^24.0.0", 177 + "yesno": "^0.4.0", 178 + "zod": "^3.23.8" 179 + }, 180 + "bin": { 181 + "lex": "dist/index.js" 182 + }, 183 + "engines": { 184 + "node": ">=18.7.0" 161 185 } 162 186 }, 163 187 "node_modules/@atproto/lex-data": { ··· 1501 1525 "integrity": "sha512-8oJ1dmzs8gbydINRB8+tSv85Tm7OZ2GYp9HSNoRGD9tU8PgqKRzdPcIw72c4koGSKhNpgFw5OaggwiGJ+XM3Dg==", 1502 1526 "license": "SEE LICENSE IN LICENSE" 1503 1527 }, 1528 + "node_modules/@ts-morph/common": { 1529 + "version": "0.25.0", 1530 + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", 1531 + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", 1532 + "dev": true, 1533 + "license": "MIT", 1534 + "dependencies": { 1535 + "minimatch": "^9.0.4", 1536 + "path-browserify": "^1.0.1", 1537 + "tinyglobby": "^0.2.9" 1538 + } 1539 + }, 1504 1540 "node_modules/@types/estree": { 1505 1541 "version": "1.0.8", 1506 1542 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", ··· 1518 1554 "undici-types": "~7.16.0" 1519 1555 } 1520 1556 }, 1557 + "node_modules/ansi-styles": { 1558 + "version": "4.3.0", 1559 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1560 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1561 + "dev": true, 1562 + "license": "MIT", 1563 + "dependencies": { 1564 + "color-convert": "^2.0.1" 1565 + }, 1566 + "engines": { 1567 + "node": ">=8" 1568 + }, 1569 + "funding": { 1570 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1571 + } 1572 + }, 1521 1573 "node_modules/await-lock": { 1522 1574 "version": "2.2.2", 1523 1575 "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", ··· 1534 1586 "@babel/core": "^7.12.10" 1535 1587 } 1536 1588 }, 1589 + "node_modules/balanced-match": { 1590 + "version": "1.0.2", 1591 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1592 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1593 + "dev": true, 1594 + "license": "MIT" 1595 + }, 1537 1596 "node_modules/baseline-browser-mapping": { 1538 1597 "version": "2.9.13", 1539 1598 "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.13.tgz", ··· 1550 1609 "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", 1551 1610 "dev": true, 1552 1611 "license": "ISC" 1612 + }, 1613 + "node_modules/brace-expansion": { 1614 + "version": "2.0.2", 1615 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1616 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1617 + "dev": true, 1618 + "license": "MIT", 1619 + "dependencies": { 1620 + "balanced-match": "^1.0.0" 1621 + } 1553 1622 }, 1554 1623 "node_modules/browserslist": { 1555 1624 "version": "4.28.1", ··· 1606 1675 ], 1607 1676 "license": "CC-BY-4.0" 1608 1677 }, 1678 + "node_modules/chalk": { 1679 + "version": "4.1.2", 1680 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1681 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1682 + "dev": true, 1683 + "license": "MIT", 1684 + "dependencies": { 1685 + "ansi-styles": "^4.1.0", 1686 + "supports-color": "^7.1.0" 1687 + }, 1688 + "engines": { 1689 + "node": ">=10" 1690 + }, 1691 + "funding": { 1692 + "url": "https://github.com/chalk/chalk?sponsor=1" 1693 + } 1694 + }, 1695 + "node_modules/code-block-writer": { 1696 + "version": "13.0.3", 1697 + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", 1698 + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", 1699 + "dev": true, 1700 + "license": "MIT" 1701 + }, 1702 + "node_modules/color-convert": { 1703 + "version": "2.0.1", 1704 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1705 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1706 + "dev": true, 1707 + "license": "MIT", 1708 + "dependencies": { 1709 + "color-name": "~1.1.4" 1710 + }, 1711 + "engines": { 1712 + "node": ">=7.0.0" 1713 + } 1714 + }, 1715 + "node_modules/color-name": { 1716 + "version": "1.1.4", 1717 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1718 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1719 + "dev": true, 1720 + "license": "MIT" 1721 + }, 1722 + "node_modules/commander": { 1723 + "version": "9.5.0", 1724 + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", 1725 + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", 1726 + "dev": true, 1727 + "license": "MIT", 1728 + "engines": { 1729 + "node": "^12.20.0 || >=14" 1730 + } 1731 + }, 1609 1732 "node_modules/convert-source-map": { 1610 1733 "version": "2.0.0", 1611 1734 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ··· 1835 1958 "node": ">=6.9.0" 1836 1959 } 1837 1960 }, 1961 + "node_modules/has-flag": { 1962 + "version": "4.0.0", 1963 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1964 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1965 + "dev": true, 1966 + "license": "MIT", 1967 + "engines": { 1968 + "node": ">=8" 1969 + } 1970 + }, 1838 1971 "node_modules/he": { 1839 1972 "version": "1.2.0", 1840 1973 "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", ··· 1920 2053 "@jridgewell/sourcemap-codec": "^1.5.5" 1921 2054 } 1922 2055 }, 2056 + "node_modules/minimatch": { 2057 + "version": "9.0.5", 2058 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 2059 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 2060 + "dev": true, 2061 + "license": "ISC", 2062 + "dependencies": { 2063 + "brace-expansion": "^2.0.1" 2064 + }, 2065 + "engines": { 2066 + "node": ">=16 || 14 >=14.17" 2067 + }, 2068 + "funding": { 2069 + "url": "https://github.com/sponsors/isaacs" 2070 + } 2071 + }, 1923 2072 "node_modules/ms": { 1924 2073 "version": "2.1.3", 1925 2074 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ··· 1983 2132 "url": "https://github.com/fb55/nth-check?sponsor=1" 1984 2133 } 1985 2134 }, 2135 + "node_modules/path-browserify": { 2136 + "version": "1.0.1", 2137 + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 2138 + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 2139 + "dev": true, 2140 + "license": "MIT" 2141 + }, 1986 2142 "node_modules/picocolors": { 1987 2143 "version": "1.1.1", 1988 2144 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ··· 2042 2198 "url": "https://opencollective.com/preact" 2043 2199 } 2044 2200 }, 2201 + "node_modules/prettier": { 2202 + "version": "3.7.4", 2203 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", 2204 + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", 2205 + "dev": true, 2206 + "license": "MIT", 2207 + "bin": { 2208 + "prettier": "bin/prettier.cjs" 2209 + }, 2210 + "engines": { 2211 + "node": ">=14" 2212 + }, 2213 + "funding": { 2214 + "url": "https://github.com/prettier/prettier?sponsor=1" 2215 + } 2216 + }, 2045 2217 "node_modules/rollup": { 2046 2218 "version": "4.55.1", 2047 2219 "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", ··· 2137 2309 "node": ">=16" 2138 2310 } 2139 2311 }, 2312 + "node_modules/supports-color": { 2313 + "version": "7.2.0", 2314 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2315 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2316 + "dev": true, 2317 + "license": "MIT", 2318 + "dependencies": { 2319 + "has-flag": "^4.0.0" 2320 + }, 2321 + "engines": { 2322 + "node": ">=8" 2323 + } 2324 + }, 2140 2325 "node_modules/tinyglobby": { 2141 2326 "version": "0.2.15", 2142 2327 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", ··· 2192 2377 "license": "MIT", 2193 2378 "bin": { 2194 2379 "tlds": "bin.js" 2380 + } 2381 + }, 2382 + "node_modules/ts-morph": { 2383 + "version": "24.0.0", 2384 + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", 2385 + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", 2386 + "dev": true, 2387 + "license": "MIT", 2388 + "dependencies": { 2389 + "@ts-morph/common": "~0.25.0", 2390 + "code-block-writer": "^13.0.3" 2195 2391 } 2196 2392 }, 2197 2393 "node_modules/tslib": { ··· 2397 2593 "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 2398 2594 "dev": true, 2399 2595 "license": "ISC" 2596 + }, 2597 + "node_modules/yesno": { 2598 + "version": "0.4.0", 2599 + "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", 2600 + "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", 2601 + "dev": true, 2602 + "license": "BSD" 2400 2603 }, 2401 2604 "node_modules/zod": { 2402 2605 "version": "3.25.76",
+3 -1
package.json
··· 6 6 "scripts": { 7 7 "dev": "vite", 8 8 "build": "tsc -b && vite build", 9 - "preview": "vite preview" 9 + "preview": "vite preview", 10 + "gen-api": "lex gen-api src/client src/lexicons/*" 10 11 }, 11 12 "dependencies": { 12 13 "@atproto/api": "^0.18.13", ··· 15 16 "preact": "^10.27.2" 16 17 }, 17 18 "devDependencies": { 19 + "@atproto/lex-cli": "^0.9.8", 18 20 "@preact/preset-vite": "^2.10.2", 19 21 "@types/node": "^24.10.1", 20 22 "typescript": "~5.9.3",
+297
src/client/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + XrpcClient, 6 + type FetchHandler, 7 + type FetchHandlerOptions, 8 + } from '@atproto/xrpc' 9 + import { schemas } from './lexicons.js' 10 + import { CID } from 'multiformats/cid' 11 + import { type OmitKey, type Un$Typed } from './util.js' 12 + import * as SocialDrydownFragrance from './types/social/drydown/fragrance.js' 13 + import * as SocialDrydownHouse from './types/social/drydown/house.js' 14 + import * as SocialDrydownReview from './types/social/drydown/review.js' 15 + 16 + export * as SocialDrydownFragrance from './types/social/drydown/fragrance.js' 17 + export * as SocialDrydownHouse from './types/social/drydown/house.js' 18 + export * as SocialDrydownReview from './types/social/drydown/review.js' 19 + 20 + export class AtpBaseClient extends XrpcClient { 21 + social: SocialNS 22 + 23 + constructor(options: FetchHandler | FetchHandlerOptions) { 24 + super(options, schemas) 25 + this.social = new SocialNS(this) 26 + } 27 + 28 + /** @deprecated use `this` instead */ 29 + get xrpc(): XrpcClient { 30 + return this 31 + } 32 + } 33 + 34 + export class SocialNS { 35 + _client: XrpcClient 36 + drydown: SocialDrydownNS 37 + 38 + constructor(client: XrpcClient) { 39 + this._client = client 40 + this.drydown = new SocialDrydownNS(client) 41 + } 42 + } 43 + 44 + export class SocialDrydownNS { 45 + _client: XrpcClient 46 + fragrance: SocialDrydownFragranceRecord 47 + house: SocialDrydownHouseRecord 48 + review: SocialDrydownReviewRecord 49 + 50 + constructor(client: XrpcClient) { 51 + this._client = client 52 + this.fragrance = new SocialDrydownFragranceRecord(client) 53 + this.house = new SocialDrydownHouseRecord(client) 54 + this.review = new SocialDrydownReviewRecord(client) 55 + } 56 + } 57 + 58 + export class SocialDrydownFragranceRecord { 59 + _client: XrpcClient 60 + 61 + constructor(client: XrpcClient) { 62 + this._client = client 63 + } 64 + 65 + async list( 66 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 67 + ): Promise<{ 68 + cursor?: string 69 + records: { uri: string; value: SocialDrydownFragrance.Record }[] 70 + }> { 71 + const res = await this._client.call('com.atproto.repo.listRecords', { 72 + collection: 'social.drydown.fragrance', 73 + ...params, 74 + }) 75 + return res.data 76 + } 77 + 78 + async get( 79 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 80 + ): Promise<{ 81 + uri: string 82 + cid: string 83 + value: SocialDrydownFragrance.Record 84 + }> { 85 + const res = await this._client.call('com.atproto.repo.getRecord', { 86 + collection: 'social.drydown.fragrance', 87 + ...params, 88 + }) 89 + return res.data 90 + } 91 + 92 + async create( 93 + params: OmitKey< 94 + ComAtprotoRepoCreateRecord.InputSchema, 95 + 'collection' | 'record' 96 + >, 97 + record: Un$Typed<SocialDrydownFragrance.Record>, 98 + headers?: Record<string, string>, 99 + ): Promise<{ uri: string; cid: string }> { 100 + const collection = 'social.drydown.fragrance' 101 + const res = await this._client.call( 102 + 'com.atproto.repo.createRecord', 103 + undefined, 104 + { collection, ...params, record: { ...record, $type: collection } }, 105 + { encoding: 'application/json', headers }, 106 + ) 107 + return res.data 108 + } 109 + 110 + async put( 111 + params: OmitKey< 112 + ComAtprotoRepoPutRecord.InputSchema, 113 + 'collection' | 'record' 114 + >, 115 + record: Un$Typed<SocialDrydownFragrance.Record>, 116 + headers?: Record<string, string>, 117 + ): Promise<{ uri: string; cid: string }> { 118 + const collection = 'social.drydown.fragrance' 119 + const res = await this._client.call( 120 + 'com.atproto.repo.putRecord', 121 + undefined, 122 + { collection, ...params, record: { ...record, $type: collection } }, 123 + { encoding: 'application/json', headers }, 124 + ) 125 + return res.data 126 + } 127 + 128 + async delete( 129 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 130 + headers?: Record<string, string>, 131 + ): Promise<void> { 132 + await this._client.call( 133 + 'com.atproto.repo.deleteRecord', 134 + undefined, 135 + { collection: 'social.drydown.fragrance', ...params }, 136 + { headers }, 137 + ) 138 + } 139 + } 140 + 141 + export class SocialDrydownHouseRecord { 142 + _client: XrpcClient 143 + 144 + constructor(client: XrpcClient) { 145 + this._client = client 146 + } 147 + 148 + async list( 149 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 150 + ): Promise<{ 151 + cursor?: string 152 + records: { uri: string; value: SocialDrydownHouse.Record }[] 153 + }> { 154 + const res = await this._client.call('com.atproto.repo.listRecords', { 155 + collection: 'social.drydown.house', 156 + ...params, 157 + }) 158 + return res.data 159 + } 160 + 161 + async get( 162 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 163 + ): Promise<{ uri: string; cid: string; value: SocialDrydownHouse.Record }> { 164 + const res = await this._client.call('com.atproto.repo.getRecord', { 165 + collection: 'social.drydown.house', 166 + ...params, 167 + }) 168 + return res.data 169 + } 170 + 171 + async create( 172 + params: OmitKey< 173 + ComAtprotoRepoCreateRecord.InputSchema, 174 + 'collection' | 'record' 175 + >, 176 + record: Un$Typed<SocialDrydownHouse.Record>, 177 + headers?: Record<string, string>, 178 + ): Promise<{ uri: string; cid: string }> { 179 + const collection = 'social.drydown.house' 180 + const res = await this._client.call( 181 + 'com.atproto.repo.createRecord', 182 + undefined, 183 + { collection, ...params, record: { ...record, $type: collection } }, 184 + { encoding: 'application/json', headers }, 185 + ) 186 + return res.data 187 + } 188 + 189 + async put( 190 + params: OmitKey< 191 + ComAtprotoRepoPutRecord.InputSchema, 192 + 'collection' | 'record' 193 + >, 194 + record: Un$Typed<SocialDrydownHouse.Record>, 195 + headers?: Record<string, string>, 196 + ): Promise<{ uri: string; cid: string }> { 197 + const collection = 'social.drydown.house' 198 + const res = await this._client.call( 199 + 'com.atproto.repo.putRecord', 200 + undefined, 201 + { collection, ...params, record: { ...record, $type: collection } }, 202 + { encoding: 'application/json', headers }, 203 + ) 204 + return res.data 205 + } 206 + 207 + async delete( 208 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 209 + headers?: Record<string, string>, 210 + ): Promise<void> { 211 + await this._client.call( 212 + 'com.atproto.repo.deleteRecord', 213 + undefined, 214 + { collection: 'social.drydown.house', ...params }, 215 + { headers }, 216 + ) 217 + } 218 + } 219 + 220 + export class SocialDrydownReviewRecord { 221 + _client: XrpcClient 222 + 223 + constructor(client: XrpcClient) { 224 + this._client = client 225 + } 226 + 227 + async list( 228 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 229 + ): Promise<{ 230 + cursor?: string 231 + records: { uri: string; value: SocialDrydownReview.Record }[] 232 + }> { 233 + const res = await this._client.call('com.atproto.repo.listRecords', { 234 + collection: 'social.drydown.review', 235 + ...params, 236 + }) 237 + return res.data 238 + } 239 + 240 + async get( 241 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 242 + ): Promise<{ uri: string; cid: string; value: SocialDrydownReview.Record }> { 243 + const res = await this._client.call('com.atproto.repo.getRecord', { 244 + collection: 'social.drydown.review', 245 + ...params, 246 + }) 247 + return res.data 248 + } 249 + 250 + async create( 251 + params: OmitKey< 252 + ComAtprotoRepoCreateRecord.InputSchema, 253 + 'collection' | 'record' 254 + >, 255 + record: Un$Typed<SocialDrydownReview.Record>, 256 + headers?: Record<string, string>, 257 + ): Promise<{ uri: string; cid: string }> { 258 + const collection = 'social.drydown.review' 259 + const res = await this._client.call( 260 + 'com.atproto.repo.createRecord', 261 + undefined, 262 + { collection, ...params, record: { ...record, $type: collection } }, 263 + { encoding: 'application/json', headers }, 264 + ) 265 + return res.data 266 + } 267 + 268 + async put( 269 + params: OmitKey< 270 + ComAtprotoRepoPutRecord.InputSchema, 271 + 'collection' | 'record' 272 + >, 273 + record: Un$Typed<SocialDrydownReview.Record>, 274 + headers?: Record<string, string>, 275 + ): Promise<{ uri: string; cid: string }> { 276 + const collection = 'social.drydown.review' 277 + const res = await this._client.call( 278 + 'com.atproto.repo.putRecord', 279 + undefined, 280 + { collection, ...params, record: { ...record, $type: collection } }, 281 + { encoding: 'application/json', headers }, 282 + ) 283 + return res.data 284 + } 285 + 286 + async delete( 287 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 288 + headers?: Record<string, string>, 289 + ): Promise<void> { 290 + await this._client.call( 291 + 'com.atproto.repo.deleteRecord', 292 + undefined, 293 + { collection: 'social.drydown.review', ...params }, 294 + { headers }, 295 + ) 296 + } 297 + }
+220
src/client/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + type LexiconDoc, 6 + Lexicons, 7 + ValidationError, 8 + type ValidationResult, 9 + } from '@atproto/lexicon' 10 + import { type $Typed, is$typed, maybe$typed } from './util.js' 11 + 12 + export const schemaDict = { 13 + SocialDrydownFragrance: { 14 + lexicon: 1, 15 + id: 'social.drydown.fragrance', 16 + defs: { 17 + main: { 18 + type: 'record', 19 + description: 'An individual fragrance with house reference', 20 + key: 'tid', 21 + record: { 22 + type: 'object', 23 + required: ['name', 'house', 'createdAt'], 24 + properties: { 25 + name: { 26 + type: 'string', 27 + minLength: 1, 28 + maxLength: 200, 29 + description: 'Fragrance name', 30 + }, 31 + house: { 32 + type: 'string', 33 + format: 'at-uri', 34 + description: 35 + 'AT URI reference to house record (at://did/social.drydown.house/rkey)', 36 + }, 37 + year: { 38 + type: 'integer', 39 + minimum: 1000, 40 + maximum: 2100, 41 + description: 'Year of release (optional)', 42 + }, 43 + createdAt: { 44 + type: 'string', 45 + format: 'datetime', 46 + description: 'Timestamp when fragrance was created', 47 + }, 48 + updatedAt: { 49 + type: 'string', 50 + format: 'datetime', 51 + description: 'Timestamp of last update', 52 + }, 53 + }, 54 + }, 55 + }, 56 + }, 57 + }, 58 + SocialDrydownHouse: { 59 + lexicon: 1, 60 + id: 'social.drydown.house', 61 + defs: { 62 + main: { 63 + type: 'record', 64 + description: 'A fragrance house or brand', 65 + key: 'tid', 66 + record: { 67 + type: 'object', 68 + required: ['name', 'createdAt'], 69 + properties: { 70 + name: { 71 + type: 'string', 72 + minLength: 1, 73 + maxLength: 100, 74 + description: 'House/brand name (must be unique per user)', 75 + }, 76 + createdAt: { 77 + type: 'string', 78 + format: 'datetime', 79 + description: 'Timestamp when house was created', 80 + }, 81 + updatedAt: { 82 + type: 'string', 83 + format: 'datetime', 84 + description: 'Timestamp of last update (for name corrections)', 85 + }, 86 + }, 87 + }, 88 + }, 89 + }, 90 + }, 91 + SocialDrydownReview: { 92 + lexicon: 1, 93 + id: 'social.drydown.review', 94 + defs: { 95 + main: { 96 + type: 'record', 97 + description: 'A single wearing review of a fragrance', 98 + key: 'tid', 99 + record: { 100 + type: 'object', 101 + required: ['fragrance', 'createdAt'], 102 + properties: { 103 + fragrance: { 104 + type: 'string', 105 + format: 'at-uri', 106 + description: 'Reference to the social.drydown.fragrance record', 107 + }, 108 + createdAt: { 109 + type: 'string', 110 + format: 'datetime', 111 + description: 'Timestamp when the review was created', 112 + }, 113 + openingRating: { 114 + type: 'integer', 115 + minimum: 1, 116 + maximum: 5, 117 + description: 'First Impression: How it smells immediately (1-5)', 118 + }, 119 + openingProjection: { 120 + type: 'integer', 121 + minimum: 1, 122 + maximum: 5, 123 + description: 124 + 'First Impression: Immediate scent bubble radius (1-5)', 125 + }, 126 + drydownRating: { 127 + type: 'integer', 128 + minimum: 1, 129 + maximum: 5, 130 + description: 'Mid-Wear: How it smells after settling (1-5)', 131 + }, 132 + midProjection: { 133 + type: 'integer', 134 + minimum: 1, 135 + maximum: 5, 136 + description: 137 + 'Mid-Wear: Scent bubble radius during mid-wear (1-5)', 138 + }, 139 + sillage: { 140 + type: 'integer', 141 + minimum: 1, 142 + maximum: 5, 143 + description: 'Mid-Wear: Trail left behind (1-5)', 144 + }, 145 + endRating: { 146 + type: 'integer', 147 + minimum: 1, 148 + maximum: 5, 149 + description: 'Final: How it smells at the end (1-5)', 150 + }, 151 + complexity: { 152 + type: 'integer', 153 + minimum: 1, 154 + maximum: 5, 155 + description: 'Final: Depth and evolution (1-5)', 156 + }, 157 + longevity: { 158 + type: 'integer', 159 + minimum: 1, 160 + maximum: 5, 161 + description: 'Final: Total duration (1-5)', 162 + }, 163 + overallRating: { 164 + type: 'integer', 165 + minimum: 1, 166 + maximum: 5, 167 + description: "Final: Holistic 'Gut Score' (1-5)", 168 + }, 169 + weightedScore: { 170 + type: 'integer', 171 + description: 'Calculated final score * 1000 (e.g. 4250 = 4.25)', 172 + }, 173 + text: { 174 + type: 'string', 175 + maxGraphemes: 255, 176 + maxLength: 3000, 177 + description: 'Written review (max 255 graphemes)', 178 + }, 179 + }, 180 + }, 181 + }, 182 + }, 183 + }, 184 + } as const satisfies Record<string, LexiconDoc> 185 + export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] 186 + export const lexicons: Lexicons = new Lexicons(schemas) 187 + 188 + export function validate<T extends { $type: string }>( 189 + v: unknown, 190 + id: string, 191 + hash: string, 192 + requiredType: true, 193 + ): ValidationResult<T> 194 + export function validate<T extends { $type?: string }>( 195 + v: unknown, 196 + id: string, 197 + hash: string, 198 + requiredType?: false, 199 + ): ValidationResult<T> 200 + export function validate( 201 + v: unknown, 202 + id: string, 203 + hash: string, 204 + requiredType?: boolean, 205 + ): ValidationResult { 206 + return (requiredType ? is$typed : maybe$typed)(v, id, hash) 207 + ? lexicons.validate(`${id}#${hash}`, v) 208 + : { 209 + success: false, 210 + error: new ValidationError( 211 + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, 212 + ), 213 + } 214 + } 215 + 216 + export const ids = { 217 + SocialDrydownFragrance: 'social.drydown.fragrance', 218 + SocialDrydownHouse: 'social.drydown.house', 219 + SocialDrydownReview: 'social.drydown.review', 220 + } as const
+42
src/client/types/social/drydown/fragrance.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 { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'social.drydown.fragrance' 12 + 13 + export interface Main { 14 + $type: 'social.drydown.fragrance' 15 + /** Fragrance name */ 16 + name: string 17 + /** AT URI reference to house record (at://did/social.drydown.house/rkey) */ 18 + house: string 19 + /** Year of release (optional) */ 20 + year?: number 21 + /** Timestamp when fragrance was created */ 22 + createdAt: string 23 + /** Timestamp of last update */ 24 + updatedAt?: string 25 + [k: string]: unknown 26 + } 27 + 28 + const hashMain = 'main' 29 + 30 + export function isMain<V>(v: V) { 31 + return is$typed(v, id, hashMain) 32 + } 33 + 34 + export function validateMain<V>(v: V) { 35 + return validate<Main & V>(v, id, hashMain, true) 36 + } 37 + 38 + export { 39 + type Main as Record, 40 + isMain as isRecord, 41 + validateMain as validateRecord, 42 + }
+38
src/client/types/social/drydown/house.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 { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'social.drydown.house' 12 + 13 + export interface Main { 14 + $type: 'social.drydown.house' 15 + /** House/brand name (must be unique per user) */ 16 + name: string 17 + /** Timestamp when house was created */ 18 + createdAt: string 19 + /** Timestamp of last update (for name corrections) */ 20 + updatedAt?: string 21 + [k: string]: unknown 22 + } 23 + 24 + const hashMain = 'main' 25 + 26 + export function isMain<V>(v: V) { 27 + return is$typed(v, id, hashMain) 28 + } 29 + 30 + export function validateMain<V>(v: V) { 31 + return validate<Main & V>(v, id, hashMain, true) 32 + } 33 + 34 + export { 35 + type Main as Record, 36 + isMain as isRecord, 37 + validateMain as validateRecord, 38 + }
+58
src/client/types/social/drydown/review.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 { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate 11 + const id = 'social.drydown.review' 12 + 13 + export interface Main { 14 + $type: 'social.drydown.review' 15 + /** Reference to the social.drydown.fragrance record */ 16 + fragrance: string 17 + /** Timestamp when the review was created */ 18 + createdAt: string 19 + /** First Impression: How it smells immediately (1-5) */ 20 + openingRating?: number 21 + /** First Impression: Immediate scent bubble radius (1-5) */ 22 + openingProjection?: number 23 + /** Mid-Wear: How it smells after settling (1-5) */ 24 + drydownRating?: number 25 + /** Mid-Wear: Scent bubble radius during mid-wear (1-5) */ 26 + midProjection?: number 27 + /** Mid-Wear: Trail left behind (1-5) */ 28 + sillage?: number 29 + /** Final: How it smells at the end (1-5) */ 30 + endRating?: number 31 + /** Final: Depth and evolution (1-5) */ 32 + complexity?: number 33 + /** Final: Total duration (1-5) */ 34 + longevity?: number 35 + /** Final: Holistic 'Gut Score' (1-5) */ 36 + overallRating?: number 37 + /** Calculated final score * 1000 (e.g. 4250 = 4.25) */ 38 + weightedScore?: number 39 + /** Written review (max 255 graphemes) */ 40 + text?: string 41 + [k: string]: unknown 42 + } 43 + 44 + const hashMain = 'main' 45 + 46 + export function isMain<V>(v: V) { 47 + return is$typed(v, id, hashMain) 48 + } 49 + 50 + export function validateMain<V>(v: V) { 51 + return validate<Main & V>(v, id, hashMain, true) 52 + } 53 + 54 + export { 55 + type Main as Record, 56 + isMain as isRecord, 57 + validateMain as validateRecord, 58 + }
+82
src/client/util.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + 5 + import { type ValidationResult } from '@atproto/lexicon' 6 + 7 + export type OmitKey<T, K extends keyof T> = { 8 + [K2 in keyof T as K2 extends K ? never : K2]: T[K2] 9 + } 10 + 11 + export type $Typed<V, T extends string = string> = V & { $type: T } 12 + export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'> 13 + 14 + export type $Type<Id extends string, Hash extends string> = Hash extends 'main' 15 + ? Id 16 + : `${Id}#${Hash}` 17 + 18 + function isObject<V>(v: V): v is V & object { 19 + return v != null && typeof v === 'object' 20 + } 21 + 22 + function is$type<Id extends string, Hash extends string>( 23 + $type: unknown, 24 + id: Id, 25 + hash: Hash, 26 + ): $type is $Type<Id, Hash> { 27 + return hash === 'main' 28 + ? $type === id 29 + : // $type === `${id}#${hash}` 30 + typeof $type === 'string' && 31 + $type.length === id.length + 1 + hash.length && 32 + $type.charCodeAt(id.length) === 35 /* '#' */ && 33 + $type.startsWith(id) && 34 + $type.endsWith(hash) 35 + } 36 + 37 + export type $TypedObject< 38 + V, 39 + Id extends string, 40 + Hash extends string, 41 + > = V extends { 42 + $type: $Type<Id, Hash> 43 + } 44 + ? V 45 + : V extends { $type?: string } 46 + ? V extends { $type?: infer T extends $Type<Id, Hash> } 47 + ? V & { $type: T } 48 + : never 49 + : V & { $type: $Type<Id, Hash> } 50 + 51 + export function is$typed<V, Id extends string, Hash extends string>( 52 + v: V, 53 + id: Id, 54 + hash: Hash, 55 + ): v is $TypedObject<V, Id, Hash> { 56 + return isObject(v) && '$type' in v && is$type(v.$type, id, hash) 57 + } 58 + 59 + export function maybe$typed<V, Id extends string, Hash extends string>( 60 + v: V, 61 + id: Id, 62 + hash: Hash, 63 + ): v is V & object & { $type?: $Type<Id, Hash> } { 64 + return ( 65 + isObject(v) && 66 + ('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true) 67 + ) 68 + } 69 + 70 + export type Validator<R = unknown> = (v: unknown) => ValidationResult<R> 71 + export type ValidatorParam<V extends Validator> = 72 + V extends Validator<infer R> ? R : never 73 + 74 + /** 75 + * Utility function that allows to convert a "validate*" utility function into a 76 + * type predicate. 77 + */ 78 + export function asPredicate<V extends Validator>(validate: V) { 79 + return function <T>(v: T): v is T & ValidatorParam<V> { 80 + return validate(v).success 81 + } 82 + }