SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%

RareIndex foundation: monorepo, shared schemas, database (55 tables), taxonomy, connector framework, web shell

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 17 days ago (Sep 7, 2026)

101 changed files +19,307 −0

added .env.example +35 −0
@@ -0,0 +1,35 @@
1 +# Copy to .env (never commit .env). All secrets are read server-side only.
2 +
3 +# PostgreSQL 17 (extensions used: pg_trgm, unaccent, vector when available)
4 +DATABASE_URL=postgres://localhost:5432/rareindex
5 +
6 +# Extraction engines (server-side only — never shipped to the browser)
7 +FIRECRAWL_API_KEY=
8 +SCRAPFLY_API_KEY=
9 +
10 +# AI providers (any subset; the model router picks what is configured)
11 +ANTHROPIC_API_KEY=
12 +OPENAI_API_KEY=
13 +# OpenAI-compatible local gateway (optional)
14 +LOCAL_LLM_BASE_URL=
15 +LOCAL_LLM_API_KEY=
16 +
17 +# Optional source API keys
18 +POKEMONTCG_API_KEY=
19 +
20 +# Web
21 +NEXT_PUBLIC_SITE_URL=http://localhost:3000
22 +SESSION_SECRET=change-me-32-bytes-minimum
23 +ADMIN_TOKEN=change-me
24 +
25 +# Public API
26 +API_PORT=8211
27 +WEB_PORT=8210
28 +
29 +# Worker
30 +WORKER_CONCURRENCY=4
31 +LOG_LEVEL=info
32 +
33 +# Transactional email (verification codes, MFA codes, alerts, digests)
34 +RESEND_API_KEY=
35 +EMAIL_FROM=RareIndex <no-reply@rareindex.io>
added .gitignore +16 −0
@@ -0,0 +1,16 @@
1 +node_modules/
2 +dist/
3 +.next/
4 +out/
5 +*.tsbuildinfo
6 +.env
7 +.env.*
8 +!.env.example
9 +logs/
10 +*.log
11 +.DS_Store
12 +coverage/
13 +.turbo/
14 +data/cache/
15 +data/raw/
16 +tmp/
added CLAUDE.md +189 −0
@@ -0,0 +1,189 @@
1 +# CLAUDE.md
2 +
3 +## RareIndex.io
4 +
5 +Global Collectibles Market Intelligence, Search, Pricing & Index Platform
6 +
7 +RareIndex.io must become the global reference platform for discovering, tracking, pricing, comparing, analyzing, and researching collectible assets.
8 +
9 +The ambition is significantly larger than a marketplace aggregator.
10 +
11 +RareIndex should become: **the Bloomberg Terminal, Google, CoinMarketCap, Zillow, and StockX of the global collectibles economy.**
12 +
13 +The platform must continuously collect public market data from marketplaces, auction houses, dealers, specialized websites, historical databases, grading companies, collector communities, manufacturer catalogs, archives, and other relevant sources.
14 +
15 +RareIndex should cover as many collectible categories as technically possible.
16 +
17 +The architecture must be built around: Firecrawl · Scrapfly · direct APIs when available · structured feeds · official datasets · auction databases · marketplace connectors · AI extraction · multimodal image analysis · entity resolution · historical sale normalization · deduplication · valuation models · global collectible indices · category indices · individual asset price histories · collection tracking · alerts · comparable-sales analysis · rarity analysis · grading normalization · liquidity analysis · AI-assisted identification.
18 +
19 +The platform language must be English-first.
20 +
21 +The application must be: extremely fast · visually exceptional · mobile-first · responsive · data dense without feeling cluttered · professional · credible · research-grade · scalable to hundreds of millions of records.
22 +
23 +> Repository companion docs: `docs/ARCHITECTURE.md` (what exists and how it fits), `docs/adr/` (decisions), `connectors/README.md` (how to add a source). Read them before changing structure.
24 +
25 +---
26 +
27 +## 1. CORE PRODUCT VISION
28 +
29 +RareIndex should answer questions such as: What is this collectible worth? How rare is it? Has its value increased? What are the latest comparable sales? Where is it currently for sale? Which marketplace has the cheapest listing? What is the global market price? What is the price trend? What is the liquidity? How often does it sell? Is the market accelerating or declining? What grade commands the highest premium? Is this listing unusually cheap or expensive? Is the collectible likely misidentified? How many examples exist? What are similar collectibles? What are the most valuable objects in this category? What collectibles are trending globally? Which categories are outperforming? Where are collectors moving capital? How has Pokémon performed versus watches? How has LEGO performed versus the S&P 500? What are the best performing collectibles over 1Y / 5Y / 10Y? What is the most liquid collectible market? What items are becoming scarce? What discontinued products are appreciating rapidly?
30 +
31 +RareIndex must transform fragmented collectible markets into structured financial-style data.
32 +
33 +## 2. PRIMARY PLATFORM MODULES
34 +
35 +Main navigation: 1 Home · 2 Explore · 3 Markets · 4 Categories · 5 RareIndex · 6 Trending · 7 Sales · 8 Listings · 9 Auctions · 10 Price History · 11 Collections · 12 Watchlist · 13 Compare · 14 Scanner · 15 AI Research · 16 Market Map · 17 Grading · 18 Auction Calendar · 19 News · 20 Data · 21 API · 22 About
36 +
37 +## 3. RAREINDEX GLOBAL INDEX
38 +
39 +Flagship index: **RareIndex Global Collectibles Index**, ticker **RARE**. Subindices: RARE-TCG, RARE-SPORT, RARE-WATCH, RARE-SNEAKER, RARE-LEGO, RARE-COMIC, RARE-GAME, RARE-ART, RARE-LUXURY, RARE-WINE, RARE-COIN, RARE-TOY, RARE-MUSIC, RARE-AUTO, RARE-TECH, RARE-BOOK, RARE-MILITARIA, RARE-SPACE, RARE-FOSSIL, RARE-DESIGN.
40 +
41 +Each index should support: current value · daily change · weekly change · monthly change · YTD · 1 year · 3 years · 5 years · 10 years · all time · volatility · drawdown · liquidity score · transaction count · median sale price · average sale price · market capitalization estimate · number of tracked assets · category breadth · momentum.
42 +
43 +## 4. COLLECTIBLE CATEGORY UNIVERSE
44 +
45 +The architecture MUST NOT hardcode a small number of collectible categories. Use an extensible taxonomy (`data/taxonomy/categories.json`, `categories` table, `taxonomy_proposals`).
46 +
47 +Every category should support: category · subcategory · franchise · brand · series · model · release · variant · year · edition · condition · grade · grading company · certification number · provenance · country · material · size · color · serial number · production quantity · rarity · population · original MSRP · estimated value · latest sale · listing price · currency · marketplace · images · description · source URLs.
48 +
49 +## 5–100. CATEGORY MASTER LIST
50 +
51 +RareIndex should attempt to cover all of the following (each is a taxonomy family; details of sets, brands, tracked attributes and graders are captured in the taxonomy files and the connectors):
52 +
53 +5 Trading Cards (Pokémon — all eras, languages, promos, error/shadowless/1st edition, graded PSA/BGS/CGC/SGC/TAG/raw; Magic: The Gathering — Alpha→current, reserved list, promos, foils, serialized; Yu-Gi-Oh!; One Piece; Disney Lorcana; other TCGs) · 6 Sports Cards (all sports, Topps/Panini/Upper Deck/Bowman/Fleer/Donruss/Score/Leaf/Futera; rookies, numbered, parallels, autos, patches, 1/1, plates) · 7 Comics (Marvel, DC, Image, Dark Horse, IDW, Boom, Archie, Valiant, Dynamite, EC; keys, variants, pedigrees; CGC/CBCS/raw) · 8 Manga · 9 Video Games (all platforms; sealed/graded/CIB/loose/prototypes) · 10 Gaming Hardware · 11 Arcade & Pinball · 12 Sneakers · 13 Watches · 14 Luxury Handbags · 15 Fashion & Streetwear · 16 LEGO · 17 Funko · 18 Designer Toys · 19 Action Figures · 20 Vintage Toys · 21 Dolls · 22 Plush · 23 Model Cars · 24 Model Trains · 25 Automobiles · 26 Motorcycles · 27 Automotive Memorabilia · 28 Formula 1 · 29 Sports Memorabilia · 30 Coins (PCGS/NGC/ANACS/ICCS) · 31 Banknotes · 32 Stamps · 33 Books · 34 Historical Documents · 35 Autographs · 36 Music · 37 Music Memorabilia · 38 Musical Instruments · 39 Movie Memorabilia · 40 Movie Posters · 41 Animation Art · 42 Art · 43 Contemporary Art (do not hardcode value assumptions) · 44 Photography · 45 Cameras · 46 Apple Collectibles · 47 Vintage Computers · 48 Computer Hardware · 49 Mobile Phones · 50 Keyboards · 51 Audio Equipment · 52 Wine · 53 Whisky · 54 Rum · 55 Cognac · 56 Perfume · 57 Jewelry · 58 Gemstones (certification + provenance) · 59 Minerals · 60 Meteorites (weight + classification) · 61 Fossils (legally traded; provenance and jurisdiction flags) · 62 Antiques · 63 Porcelain · 64 Glass & Crystal · 65 Silver · 66 Clocks · 67 Pens · 68 Lighters · 69 Advertising Collectibles · 70 Disney · 71 Sanrio · 72 Star Wars · 73 Marvel · 74 DC · 75 Harry Potter · 76 Lord of the Rings · 77 Warhammer · 78 Gundam · 79 Travel · 80 Airline · 81 Aviation · 82 Space · 83 Militaria (legal; compliance restrictions implemented) · 84 Medals · 85 Olympic · 86 Maps · 87 Postcards · 88 License Plates · 89 Pins · 90 Board Games · 91 Chess · 92 Playing Cards · 93 Casino Memorabilia (where legal) · 94 Political Memorabilia (neutral presentation only) · 95 Royal Memorabilia · 96 Scientific Instruments · 97 Typewriters · 98 Vending Machines · 99 Historic Corporate Memorabilia · 100 Digital Collectibles (separate valuation model).
54 +
55 +## 101. FUTURE CATEGORY DISCOVERY
56 +
57 +RareIndex must automatically discover new collectible categories: crawl marketplaces → identify recurring product clusters → detect categories missing from the taxonomy → measure transaction volume → propose taxonomy nodes → admin queue → approval → auto-generate source connectors and schemas. The taxonomy must evolve continuously.
58 +
59 +## 102. CONNECTOR ARCHITECTURE
60 +
61 +Every source uses the standardized interface (`packages/connectors/src/types.ts`):
62 +
63 +```ts
64 +interface RareIndexConnector {
65 + meta: ConnectorMeta // id, name, categories, countries, languages…
66 + crawl(ctx): AsyncIterable<RawRecordInput>
67 + normalize(raw): Promise<NormalizedRecord[]>
68 + healthCheck(ctx): Promise<ConnectorHealth>
69 +}
70 +```
71 +
72 +## 103. FIRECRAWL
73 +
74 +Preferred engine for websites reliably parsed with HTML / markdown / structured extraction / sitemap discovery / crawling / search / dynamic rendering: category pages, search results, public catalogs, auction archives, sold-item databases, dealer inventory, pricing guides, manufacturer archives, grading info, encyclopedias, editorial market data. Output is converted into canonical JSON.
75 +
76 +## 104. SCRAPFLY
77 +
78 +Secondary extraction engine, difficult-site connector, dynamic rendering layer, proxy/geo routing layer, anti-bot-compatible access layer where legally permitted, fallback when ordinary extraction fails.
79 +
80 +Routing: **Direct API → Structured Feed → Firecrawl → Scrapfly → Browser Extraction → Manual Review.**
81 +
82 +Do NOT attempt to circumvent authentication, paywalls, CAPTCHAs, access controls, or other security protections unlawfully. Respect applicable laws, source terms, robots policies where applicable, copyright, privacy, rate limits, authentication boundaries.
83 +
84 +## 105. CONNECTOR FALLBACK ENGINE
85 +
86 +Every connector produces health metrics (`ConnectorHealth`: status, success_rate_24h, pages_attempted/success, firecrawl_success_rate, scrapfly_fallback_rate, parse_failure_rate, last_success, schema_version). Automatically detect page redesigns, selector failures, missing fields, duplicate explosion, abnormal price distributions, blocked requests, empty pages, unexpected redirects, currency parsing problems.
87 +
88 +## 106. CONNECTOR REGISTRY
89 +
90 +`/connectors/registry.json` (generated from per-connector `meta.json`): id, displayName, sourceType, enginePriority, categories, regions, languages, currency, supportsListings/Sold/Auctions/Images, refreshFrequencyMinutes.
91 +
92 +## 107. SOURCE TYPES
93 +
94 +marketplace · auction_house · dealer · pricing_guide · grading_company · manufacturer · museum · archive · catalog · collector_database · community · forum · news · analytics_provider · registry · official_source
95 +
96 +## 108. RAW INGESTION LAYER
97 +
98 +Never directly write scraped records into final product tables. Source → Raw payload → Extraction → Raw JSON → Normalization → Entity Resolution → Deduplication → Price Validation → Canonical Asset Match → Sale/Listing Database → Indices & Analytics. Raw records are immutable.
99 +
100 +## 109–111. CANONICAL MODELS
101 +
102 +Canonical asset (`assets` + `asset_variants`), sale (`sales`: source, url, type, date, price, currency, price_usd, buyer premium, condition, grade, location, images, raw title, confidence) and listing (`listings`: asking price, seller, source, location, shipping, condition, grade, dates, availability, quantity, seller reputation, images, description). **Listing price MUST NOT automatically equal market value.**
103 +
104 +## 112. ENTITY RESOLUTION
105 +
106 +Different sources describe the same collectible differently; they must resolve to the same canonical asset variant using deterministic identifiers, model numbers, card numbers, sets, year, UPC/ISBN/SKU, certification, serial, visual similarity, text and multimodal embeddings, LLM verification.
107 +
108 +## 113–114. IMAGE INTELLIGENCE & SCANNER
109 +
110 +Image pipelines for duplicate detection, identification, edition recognition, condition estimation, box/seal recognition; multimodal embeddings stored in pgvector. The Scanner accepts photos or a marketplace URL and estimates category, item, edition, year, variation, condition, grade range, rarity, value range, sales, comps and listings — always with confidence, never claiming professional authentication.
111 +
112 +## 115–124. PRICE ENGINE & SCORES
113 +
114 +Never use only the average. Compute latest sale, median 5/10/20, volume-weighted, trimmed mean, exponentially weighted, comparable-, grade- and condition-adjusted prices → **RIV (RareIndex Valuation)** with low/high estimate and confidence. Flag (never silently delete) outliers, fake/cancelled/bundle sales, misidentified items; keep audit logs. Category-specific condition scales; grading companies tracked separately with empirical premiums (PSA 10 ≠ BGS 10 ≠ CGC 10). Population reports → gem rate, scarcity, Population Momentum. Rarity Score 0–100 (unknown when data missing). Liquidity Score 0–100. Momentum 7d/30d/90d/1y. Value Opportunity Score (analytical data, not investment advice). Market capitalization only with methodology and confidence.
115 +
116 +## 125–136. PAGES, SEARCH, PORTFOLIO, ALERTS, AUCTIONS, COMPS, CURRENCY
117 +
118 +Category market pages (`/markets/<slug>`), world-class asset pages (header, metrics, charts, tabs Overview/Sales/Listings/Grades/Population/Images/History/Comparables/AI Analysis/Sources), natural-language global search, conversational AI research over structured internal data (no hallucination), collection portfolios with analytics, watchlists, alerts, auction calendar, comparable sales with similarity scores, price distribution (min/p25/median/p75/max/trimmed mean). Store native currency + historical FX + USD/CAD/EUR/GBP/JPY; never convert historical sales at today's rate.
119 +
120 +## 137–159. PLATFORM
121 +
122 +Internationalization later (canonical data language neutral). Hybrid search (PostgreSQL FTS + vectors, OpenSearch later). PostgreSQL + object storage + Redis/queues + vector storage. Event-driven pipeline (page_discovered, page_crawled, listing_created, listing_updated, sale_detected, entity_matched, valuation_updated, index_updated). Dynamic crawl scheduling by source importance. Full data provenance. Source trust score. `/admin/connectors` control center. AI connector repair with fixture validation. Connector test suite with fixtures. Marketplace and image deduplication (`cross_listing_group_id`, perceptual hashes). Fraud signals (analytical, never definitive). Data Quality 0–100. Market heatmap, trending algorithm, RareIndex 100, record sales, Rare Radar, market news, public API `/v1/...` with tiers, exports (CSV/JSON/Excel/PDF).
123 +
124 +## 160–166. DESIGN
125 +
126 +Institutional financial platform × modern luxury marketplace (Bloomberg, Sotheby's, Christie's, StockX, Linear, Stripe, Apple). Neutral base; accents only for gains, losses, index identity, rarity, alerts; dark mode. Homepage: "The Global Market for Collectibles" — search — live tape — stats — discovery grid (Trending Now, Biggest Movers, Record Sales, Rare Finds, Auctions Ending Soon, Most Watched, Newly Added, Market Insights). Mobile is not an afterthought. Performance: homepage interactive < 2 s, cached search < 300 ms, optimized images, SSR, CDN caching.
127 +
128 +## 167–171. TECH, AI ROUTER, COST, BUDGET, CHANGE DETECTION
129 +
130 +Next.js/TypeScript/React/Tailwind/TanStack; Node/TypeScript services; PostgreSQL; Redis/queues; object storage; Firecrawl + Scrapfly; AI provider abstraction (`ModelProvider { complete, extract, embed, vision }`) never locked to one vendor; cost tracking per connector/category/source/record/user/endpoint (`/admin/costs`); scraping budget engine (ETag, Last-Modified, content hashing, change-frequency estimates); listing change detection (New, Price Changed, Sold, Removed, Relisted, Auction Ended) with event history.
131 +
132 +## 172–179. BACKFILL, SEO, AUTH, PRIVACY, COMPLIANCE
133 +
134 +Aggressive but legitimate historical backfills with real source timestamps. Indexable SEO pages with Schema.org (Product, Offer, AggregateOffer, CollectionPage, Article, BreadcrumbList). Auth: email, Google, Apple, passkeys. Private collections private by default. Disclaimers: valuations are estimates; listing prices are not confirmed transactions; past performance does not guarantee future results; RareIndex does not authenticate items. Source attribution. **Prohibited automation**: no bypassing logins/paywalls/CAPTCHAs/authentication controls, no private data scraping, no unlawful circumvention.
135 +
136 +## 180–183. PHASES
137 +
138 +Phase 1 foundation: Pokémon, Sports Cards, Magic, Yu-Gi-Oh!, Watches, Sneakers, LEGO, Comics, Video Games, Funko/Designer Toys — canonical models, connectors, sale ingestion, listings, search, price engine, asset pages, category pages. Phase 2: coins, handbags, wine, whisky, cars, music, toys, books, art + portfolio, alerts, scanner, indices. Phase 3: all categories, AI category discovery, international sources, API, institutional analytics, backfills, research terminal. Phase 4: RareIndex Terminal (screener, correlation matrix, liquidity scanner, volatility ranking, historical returns, factor analysis, anomaly scanner).
139 +
140 +## 184–191. TERMINAL, FACTORS, CORRELATION, BENCHMARKS, DATA SCIENCE, CONFIDENCE
141 +
142 +Screener filters; collectible factors (research relationships without asserting causality); category correlation matrix; benchmarking vs S&P 500, NASDAQ, gold, Bitcoin, inflation, real estate (explain liquidity/methodology differences); research models (hedonic, nearest-neighbor comps, gradient boosting, Bayesian, time-series smoothing, repeat-sales, hierarchical, multimodal embeddings). Every statistic shows sample size, confidence, last update.
143 +
144 +## 192. DO NOT INVENT DATA
145 +
146 +Absolute rule. Never fabricate sale prices, production counts, grading populations, market caps, auctions, listing availability. When data is missing, "Data unavailable" is better than hallucination.
147 +
148 +## 193–194. OBSERVABILITY & SECURITY
149 +
150 +Structured logs, traces, metrics, error reporting (crawl latency, parsing errors, DB latency, AI cost, connector health, queue lag, valuation failures). Strict secrets management, server-side credentials only, encryption, rate limits, abuse prevention, input validation. Never expose Firecrawl, Scrapfly, AI keys or database credentials.
151 +
152 +## 195. REPOSITORY STRUCTURE
153 +
154 +```
155 +rareindex/
156 +├── apps/ (web, api, admin→ inside web /admin for Phase 1)
157 +├── packages/ (database, taxonomy, valuation, search, indices, ai, connectors, shared)
158 +├── workers/ (crawler, normalizer, entity-resolution, valuation, image-processing, indices)
159 +├── connectors/ (firecrawl, scrapfly, api, feeds, registry.json)
160 +├── data/ (taxonomy, fixtures)
161 +├── docs/ · scripts/ · CLAUDE.md
162 +```
163 +
164 +## 196. ENGINEERING RULES FOR CLAUDE CODE
165 +
166 +1 Inspect the existing repository first. 2 Never overwrite working functionality unnecessarily. 3 Prefer reusable abstractions. 4 Avoid monolithic files. 5 Keep connector logic isolated. 6 Keep source-specific logic outside the canonical domain model. 7 Add tests for every connector. 8 Add migrations for DB changes. 9 Keep TypeScript strict. 10 Validate external payloads. 11 Use Zod. 12 Log extraction failures. 13 Never swallow exceptions silently. 14 Build responsive UI from the beginning. 15 Optimize database indexes. 16 Avoid premature microservices. 17 Document major architectural decisions. 18 Never hardcode credentials. 19 Never invent fake production data. 20 Preserve traceability from every normalized datapoint to its source.
167 +
168 +## 197. CONNECTOR IMPLEMENTATION RULE
169 +
170 +A new marketplace requires only: connector metadata · extraction schema · normalization adapter · test fixtures. The rest of the ingestion pipeline works automatically.
171 +
172 +## 198–199. ROUTER & EXTRACTION QUALITY
173 +
174 +`fetchCollectiblePage`: official API → Firecrawl (quality ≥ MIN) → Scrapfly (quality ≥ MIN) → requiresReview. Quality weights: Title 20% · Price 20% · Status 15% · Date 10% · Images 10% · Description 5% · Identifiers 10% · Category 5% · Currency 5%.
175 +
176 +## 200–202. ULTIMATE GOAL, PRINCIPLE, MANDATE
177 +
178 +One canonical asset graph containing every collectible, every important variation, every observable listing, every verified historical sale, and every meaningful market signal that can legally and reliably be obtained. Always prioritize data quality over superficial features; admit insufficient evidence rather than display a number; build the intelligence platform, not a generic marketplace. Build incrementally but architect from day one for millions of models, hundreds of millions of observations, thousands of sources, global currencies, multilingual descriptions and billions of image references. RareIndex is the global data layer for collectible assets.
179 +
180 +---
181 +
182 +## Repository conventions (added by the implementation)
183 +
184 +- Package manager `pnpm`; `pnpm typecheck`, `pnpm test`, `pnpm build`. Workers run with `tsx`.
185 +- Secrets only in `.env` (git-ignored) or the `mld` manifest on the cluster. Never in code or fixtures.
186 +- Add a connector: `connectors/<engine>/<id>/{meta.json,index.ts,index.test.ts}` + `data/fixtures/<id>/*.json`, then `pnpm tsx scripts/build-registry.ts` and `pnpm db:seed`.
187 +- DB changes: edit `packages/database/src/schema/*`, run `pnpm db:generate`, commit the SQL migration.
188 +- UI: dense, neutral, tabular numbers (`num` utility), `<Unavailable/>` for missing evidence, every number with its sample size / confidence / freshness when it is an estimate.
189 +- Member accounts: email + password, email verification code and multi-factor code delivered through Resend (`RESEND_API_KEY`, `EMAIL_FROM`).
added apps/api/package.json +1 −0
@@ -0,0 +1 @@
1 +{"name":"@rareindex/api","version":"0.1.0","private":true,"type":"module","scripts":{"typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run --passWithNoTests","build":"tsc -p tsconfig.json --noEmit"},"dependencies":{"@rareindex/shared":"workspace:*"},"devDependencies":{"@types/node":"^24.0.0","typescript":"^5.9.3","vitest":"^3.2.0"}}
added apps/web/.gitignore +41 −0
@@ -0,0 +1,41 @@
1 +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 +
3 +# dependencies
4 +/node_modules
5 +/.pnp
6 +.pnp.*
7 +.yarn/*
8 +!.yarn/patches
9 +!.yarn/plugins
10 +!.yarn/releases
11 +!.yarn/versions
12 +
13 +# testing
14 +/coverage
15 +
16 +# next.js
17 +/.next/
18 +/out/
19 +
20 +# production
21 +/build
22 +
23 +# misc
24 +.DS_Store
25 +*.pem
26 +
27 +# debug
28 +npm-debug.log*
29 +yarn-debug.log*
30 +yarn-error.log*
31 +.pnpm-debug.log*
32 +
33 +# env files (can opt-in for committing if needed)
34 +.env*
35 +
36 +# vercel
37 +.vercel
38 +
39 +# typescript
40 +*.tsbuildinfo
41 +next-env.d.ts
added apps/web/eslint.config.mjs +18 −0
@@ -0,0 +1,18 @@
1 +import { defineConfig, globalIgnores } from "eslint/config";
2 +import nextVitals from "eslint-config-next/core-web-vitals";
3 +import nextTs from "eslint-config-next/typescript";
4 +
5 +const eslintConfig = defineConfig([
6 + ...nextVitals,
7 + ...nextTs,
8 + // Override default ignores of eslint-config-next.
9 + globalIgnores([
10 + // Default ignores of eslint-config-next:
11 + ".next/**",
12 + "out/**",
13 + "build/**",
14 + "next-env.d.ts",
15 + ]),
16 +]);
17 +
18 +export default eslintConfig;
added apps/web/next.config.ts +31 −0
@@ -0,0 +1,31 @@
1 +import type { NextConfig } from 'next';
2 +
3 +const nextConfig: NextConfig = {
4 + reactStrictMode: true,
5 + poweredByHeader: false,
6 + // Workspace packages are consumed as TypeScript sources.
7 + transpilePackages: ['@rareindex/shared', '@rareindex/database', '@rareindex/taxonomy', '@rareindex/connectors'],
8 + serverExternalPackages: ['postgres', 'pino', 'cheerio'],
9 + images: {
10 + remotePatterns: [{ protocol: 'https', hostname: '**' }],
11 + formats: ['image/avif', 'image/webp'],
12 + },
13 + experimental: {
14 + optimizePackageImports: ['lucide-react'],
15 + },
16 + async headers() {
17 + return [
18 + {
19 + source: '/(.*)',
20 + headers: [
21 + { key: 'X-Content-Type-Options', value: 'nosniff' },
22 + { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
23 + { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
24 + { key: 'Permissions-Policy', value: 'camera=(self), microphone=(), geolocation=()' },
25 + ],
26 + },
27 + ];
28 + },
29 +};
30 +
31 +export default nextConfig;
added apps/web/package.json +37 −0
@@ -0,0 +1,37 @@
1 +{
2 + "name": "@rareindex/web",
3 + "version": "0.1.0",
4 + "private": true,
5 + "scripts": {
6 + "dev": "next dev -p 3000",
7 + "build": "next build",
8 + "start": "next start",
9 + "lint": "eslint",
10 + "typecheck": "tsc -p tsconfig.json --noEmit",
11 + "test": "vitest run --passWithNoTests"
12 + },
13 + "dependencies": {
14 + "@rareindex/connectors": "workspace:*",
15 + "@rareindex/database": "workspace:*",
16 + "@rareindex/shared": "workspace:*",
17 + "@rareindex/taxonomy": "workspace:*",
18 + "@tanstack/react-query": "^5.90.0",
19 + "lucide-react": "^1.0.0",
20 + "next": "16.3.4",
21 + "react": "19.2.8",
22 + "react-dom": "19.2.8",
23 + "server-only": "^0.0.1",
24 + "zod": "^4.0.0"
25 + },
26 + "devDependencies": {
27 + "@tailwindcss/postcss": "^4",
28 + "@types/node": "^24.0.0",
29 + "@types/react": "^19",
30 + "@types/react-dom": "^19",
31 + "eslint": "^9",
32 + "eslint-config-next": "16.3.4",
33 + "tailwindcss": "^4",
34 + "typescript": "^5.9.3",
35 + "vitest": "^3.2.0"
36 + }
37 +}
added apps/web/postcss.config.mjs +7 −0
@@ -0,0 +1,7 @@
1 +const config = {
2 + plugins: {
3 + "@tailwindcss/postcss": {},
4 + },
5 +};
6 +
7 +export default config;
added apps/web/src/app/error.tsx +17 −0
@@ -0,0 +1,17 @@
1 +'use client';
2 +
3 +import { EmptyState } from '@/components/ui/primitives';
4 +
5 +export default function ErrorPage({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
6 + return (
7 + <EmptyState
8 + title="Something went wrong"
9 + description={error.digest ? `Reference ${error.digest}` : 'An unexpected error occurred while rendering this page.'}
10 + action={
11 + <button type="button" onClick={reset} className="rounded-md border border-border px-3 py-1.5 text-xs font-medium hover:bg-inset">
12 + Try again
13 + </button>
14 + }
15 + />
16 + );
17 +}
added apps/web/src/app/favicon.ico +0 −0

Binary file not shown.

added apps/web/src/app/globals.css +211 −0
@@ -0,0 +1,211 @@
1 +@import "tailwindcss";
2 +
3 +/*
4 + RareIndex design language (§160–§161): institutional market terminal × modern luxury house.
5 + Neutral base, accents reserved for gains / losses / index identity / rarity / alerts.
6 + Light and dark are first-class; `data-theme` on <html> overrides system preference.
7 +*/
8 +
9 +:root {
10 + --ri-bg: #fafaf9;
11 + --ri-bg-elevated: #ffffff;
12 + --ri-bg-sunken: #f4f4f3;
13 + --ri-bg-inset: #ececea;
14 + --ri-fg: #0b0b0c;
15 + --ri-fg-muted: #55555b;
16 + --ri-fg-subtle: #8a8a91;
17 + --ri-border: #e4e4e2;
18 + --ri-border-strong: #c9c9c5;
19 + --ri-accent: #0b0b0c;
20 + --ri-accent-fg: #ffffff;
21 + --ri-gain: #0f8a4a;
22 + --ri-gain-bg: #e6f5ec;
23 + --ri-loss: #c8262c;
24 + --ri-loss-bg: #fbe9ea;
25 + --ri-flat: #6b6b72;
26 + --ri-index: #1f3a8a;
27 + --ri-index-bg: #e8edfa;
28 + --ri-rarity: #7c3aed;
29 + --ri-rarity-bg: #f0e9fe;
30 + --ri-alert: #b45309;
31 + --ri-alert-bg: #fdf1e3;
32 + --ri-gold: #a16207;
33 + --ri-ring: rgba(11, 11, 12, 0.18);
34 + --ri-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 1px rgba(0, 0, 0, 0.03);
35 + --ri-shadow-lg: 0 10px 30px -12px rgba(0, 0, 0, 0.18);
36 + --ri-radius: 6px;
37 + --ri-radius-sm: 4px;
38 + --ri-radius-lg: 10px;
39 + color-scheme: light;
40 +}
41 +
42 +html[data-theme="dark"] {
43 + --ri-bg: #0b0c0e;
44 + --ri-bg-elevated: #121316;
45 + --ri-bg-sunken: #08090a;
46 + --ri-bg-inset: #1a1b1f;
47 + --ri-fg: #f2f2f0;
48 + --ri-fg-muted: #a8a8ad;
49 + --ri-fg-subtle: #6f6f76;
50 + --ri-border: #24252a;
51 + --ri-border-strong: #3a3b42;
52 + --ri-accent: #f2f2f0;
53 + --ri-accent-fg: #0b0c0e;
54 + --ri-gain: #34c47c;
55 + --ri-gain-bg: rgba(52, 196, 124, 0.12);
56 + --ri-loss: #ef5a5f;
57 + --ri-loss-bg: rgba(239, 90, 95, 0.12);
58 + --ri-flat: #8b8b93;
59 + --ri-index: #7c9cf5;
60 + --ri-index-bg: rgba(124, 156, 245, 0.12);
61 + --ri-rarity: #a78bfa;
62 + --ri-rarity-bg: rgba(167, 139, 250, 0.12);
63 + --ri-alert: #f0a34c;
64 + --ri-alert-bg: rgba(240, 163, 76, 0.12);
65 + --ri-gold: #d4a53a;
66 + --ri-ring: rgba(242, 242, 240, 0.22);
67 + --ri-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
68 + --ri-shadow-lg: 0 14px 40px -14px rgba(0, 0, 0, 0.7);
69 + color-scheme: dark;
70 +}
71 +
72 +@media (prefers-color-scheme: dark) {
73 + html:not([data-theme="light"]) {
74 + --ri-bg: #0b0c0e;
75 + --ri-bg-elevated: #121316;
76 + --ri-bg-sunken: #08090a;
77 + --ri-bg-inset: #1a1b1f;
78 + --ri-fg: #f2f2f0;
79 + --ri-fg-muted: #a8a8ad;
80 + --ri-fg-subtle: #6f6f76;
81 + --ri-border: #24252a;
82 + --ri-border-strong: #3a3b42;
83 + --ri-accent: #f2f2f0;
84 + --ri-accent-fg: #0b0c0e;
85 + --ri-gain: #34c47c;
86 + --ri-gain-bg: rgba(52, 196, 124, 0.12);
87 + --ri-loss: #ef5a5f;
88 + --ri-loss-bg: rgba(239, 90, 95, 0.12);
89 + --ri-flat: #8b8b93;
90 + --ri-index: #7c9cf5;
91 + --ri-index-bg: rgba(124, 156, 245, 0.12);
92 + --ri-rarity: #a78bfa;
93 + --ri-rarity-bg: rgba(167, 139, 250, 0.12);
94 + --ri-alert: #f0a34c;
95 + --ri-alert-bg: rgba(240, 163, 76, 0.12);
96 + --ri-gold: #d4a53a;
97 + --ri-ring: rgba(242, 242, 240, 0.22);
98 + --ri-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
99 + --ri-shadow-lg: 0 14px 40px -14px rgba(0, 0, 0, 0.7);
100 + color-scheme: dark;
101 + }
102 +}
103 +
104 +@theme inline {
105 + --color-bg: var(--ri-bg);
106 + --color-elevated: var(--ri-bg-elevated);
107 + --color-sunken: var(--ri-bg-sunken);
108 + --color-inset: var(--ri-bg-inset);
109 + --color-fg: var(--ri-fg);
110 + --color-muted: var(--ri-fg-muted);
111 + --color-subtle: var(--ri-fg-subtle);
112 + --color-border: var(--ri-border);
113 + --color-border-strong: var(--ri-border-strong);
114 + --color-accent: var(--ri-accent);
115 + --color-accent-fg: var(--ri-accent-fg);
116 + --color-gain: var(--ri-gain);
117 + --color-gain-bg: var(--ri-gain-bg);
118 + --color-loss: var(--ri-loss);
119 + --color-loss-bg: var(--ri-loss-bg);
120 + --color-flat: var(--ri-flat);
121 + --color-index: var(--ri-index);
122 + --color-index-bg: var(--ri-index-bg);
123 + --color-rarity: var(--ri-rarity);
124 + --color-rarity-bg: var(--ri-rarity-bg);
125 + --color-alert: var(--ri-alert);
126 + --color-alert-bg: var(--ri-alert-bg);
127 + --color-gold: var(--ri-gold);
128 + --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
129 + --font-mono: var(--font-geist-mono), ui-monospace, "SF Mono", Menlo, monospace;
130 + --radius-sm: var(--ri-radius-sm);
131 + --radius-md: var(--ri-radius);
132 + --radius-lg: var(--ri-radius-lg);
133 + --shadow-card: var(--ri-shadow);
134 + --shadow-pop: var(--ri-shadow-lg);
135 +}
136 +
137 +@layer base {
138 + html {
139 + height: 100%;
140 + -webkit-text-size-adjust: 100%;
141 + scroll-behavior: smooth;
142 + }
143 + body {
144 + min-height: 100%;
145 + background: var(--ri-bg);
146 + color: var(--ri-fg);
147 + font-family: var(--font-sans);
148 + font-feature-settings: "cv11", "ss01", "tnum";
149 + text-rendering: optimizeLegibility;
150 + -webkit-font-smoothing: antialiased;
151 + }
152 + ::selection {
153 + background: var(--ri-index-bg);
154 + }
155 + :focus-visible {
156 + outline: 2px solid var(--ri-ring);
157 + outline-offset: 2px;
158 + }
159 + input,
160 + select,
161 + textarea {
162 + font-size: 16px; /* prevents iOS zoom */
163 + }
164 + @media (min-width: 768px) {
165 + input,
166 + select,
167 + textarea {
168 + font-size: 14px;
169 + }
170 + }
171 + h1, h2, h3, h4 {
172 + letter-spacing: -0.015em;
173 + }
174 +}
175 +
176 +@utility num {
177 + font-variant-numeric: tabular-nums lining-nums;
178 + font-feature-settings: "tnum", "lnum";
179 +}
180 +@utility mono-num {
181 + font-family: var(--font-mono);
182 + font-variant-numeric: tabular-nums;
183 +}
184 +@utility hairline {
185 + border-color: var(--ri-border);
186 +}
187 +@utility card {
188 + background: var(--ri-bg-elevated);
189 + border: 1px solid var(--ri-border);
190 + border-radius: var(--ri-radius);
191 + box-shadow: var(--ri-shadow);
192 +}
193 +@utility scrollbar-none {
194 + scrollbar-width: none;
195 + &::-webkit-scrollbar { display: none; }
196 +}
197 +@utility safe-bottom {
198 + padding-bottom: max(env(safe-area-inset-bottom), 0.75rem);
199 +}
200 +
201 +@keyframes ri-tape {
202 + from { transform: translateX(0); }
203 + to { transform: translateX(-50%); }
204 +}
205 +.tape-track {
206 + animation: ri-tape 60s linear infinite;
207 +}
208 +.tape-track:hover { animation-play-state: paused; }
209 +@media (prefers-reduced-motion: reduce) {
210 + .tape-track { animation: none; }
211 +}
added apps/web/src/app/healthz/route.ts +14 −0
@@ -0,0 +1,14 @@
1 +import { NextResponse } from 'next/server';
2 +import { getSql } from '@rareindex/database';
3 +
4 +export const dynamic = 'force-dynamic';
5 +
6 +export async function GET() {
7 + try {
8 + const sql = getSql();
9 + const [row] = await sql`select now() as now`;
10 + return NextResponse.json({ ok: true, service: 'rareindex-web', db: Boolean(row), time: new Date().toISOString() });
11 + } catch (err) {
12 + return NextResponse.json({ ok: false, service: 'rareindex-web', error: err instanceof Error ? err.message : String(err) }, { status: 503 });
13 + }
14 +}
added apps/web/src/app/layout.tsx +46 −0
@@ -0,0 +1,46 @@
1 +import type { Metadata, Viewport } from 'next';
2 +import { Geist, Geist_Mono } from 'next/font/google';
3 +import './globals.css';
4 +import { SiteHeader } from '@/components/layout/site-header';
5 +import { SiteFooter } from '@/components/layout/site-footer';
6 +import { THEME_SCRIPT } from '@/components/layout/theme-toggle';
7 +
8 +const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'] });
9 +const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'] });
10 +
11 +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://www.rareindex.io';
12 +
13 +export const metadata: Metadata = {
14 + metadataBase: new URL(SITE_URL),
15 + title: { default: 'RareIndex — The Global Market for Collectibles', template: '%s · RareIndex' },
16 + description: 'Discover, price, track and analyze collectible assets across the world’s marketplaces and auction houses.',
17 + applicationName: 'RareIndex',
18 + openGraph: { type: 'website', siteName: 'RareIndex', url: SITE_URL, title: 'RareIndex — The Global Market for Collectibles', description: 'Prices, sales, listings, rarity, liquidity and indices for collectible assets.' },
19 + twitter: { card: 'summary_large_image', title: 'RareIndex', description: 'The global market for collectibles.' },
20 + robots: { index: true, follow: true },
21 +};
22 +
23 +export const viewport: Viewport = {
24 + width: 'device-width',
25 + initialScale: 1,
26 + viewportFit: 'cover',
27 + themeColor: [
28 + { media: '(prefers-color-scheme: light)', color: '#fafaf9' },
29 + { media: '(prefers-color-scheme: dark)', color: '#0b0c0e' },
30 + ],
31 +};
32 +
33 +export default function RootLayout({ children }: { children: React.ReactNode }) {
34 + return (
35 + <html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} suppressHydrationWarning>
36 + <head>
37 + <script dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }} />
38 + </head>
39 + <body className="flex min-h-full flex-col">
40 + <SiteHeader />
41 + <main className="mx-auto w-full max-w-[1440px] flex-1 px-4 pb-16 pt-4 sm:px-6">{children}</main>
42 + <SiteFooter />
43 + </body>
44 + </html>
45 + );
46 +}
added apps/web/src/app/not-found.tsx +16 −0
@@ -0,0 +1,16 @@
1 +import Link from 'next/link';
2 +import { EmptyState } from '@/components/ui/primitives';
3 +
4 +export default function NotFound() {
5 + return (
6 + <EmptyState
7 + title="Page not found"
8 + description="The asset, market or page you requested does not exist or has been merged into another record."
9 + action={
10 + <Link href="/" className="rounded-md border border-border px-3 py-1.5 text-xs font-medium hover:bg-inset">
11 + Back to RareIndex
12 + </Link>
13 + }
14 + />
15 + );
16 +}
added apps/web/src/app/page.tsx +6 −0
@@ -0,0 +1,6 @@
1 +import { EmptyState } from '@/components/ui/primitives';
2 +
3 +// Placeholder — replaced by the homepage module (hero, live tape, discovery grid).
4 +export default function HomePage() {
5 + return <EmptyState title="RareIndex is initialising" description="The global market for collectibles. Data pipelines are being connected." />;
6 +}
added apps/web/src/components/layout/header-search.tsx +32 −0
@@ -0,0 +1,32 @@
1 +'use client';
2 +
3 +import { useRouter } from 'next/navigation';
4 +import { Search } from 'lucide-react';
5 +import { useState } from 'react';
6 +
7 +/** Compact header search; the full search experience lives at /search (owned by the search module). */
8 +export function HeaderSearch() {
9 + const router = useRouter();
10 + const [q, setQ] = useState('');
11 + return (
12 + <form
13 + role="search"
14 + className="relative hidden items-center md:flex"
15 + onSubmit={(e) => {
16 + e.preventDefault();
17 + const query = q.trim();
18 + if (query) router.push(`/search?q=${encodeURIComponent(query)}`);
19 + }}
20 + >
21 + <Search className="pointer-events-none absolute left-2.5 h-3.5 w-3.5 text-subtle" />
22 + <input
23 + type="search"
24 + value={q}
25 + onChange={(e) => setQ(e.target.value)}
26 + placeholder="Search cards, watches, games, sneakers, LEGO…"
27 + className="h-8 w-64 rounded-md border border-border bg-sunken pl-8 pr-2 text-[13px] text-fg placeholder:text-subtle focus:w-80 focus:border-border-strong focus:outline-none transition-[width] xl:w-80 xl:focus:w-96"
28 + aria-label="Search collectibles"
29 + />
30 + </form>
31 + );
32 +}
added apps/web/src/components/layout/mobile-nav.tsx +56 −0
@@ -0,0 +1,56 @@
1 +'use client';
2 +
3 +import Link from 'next/link';
4 +import { useEffect, useState } from 'react';
5 +import { usePathname } from 'next/navigation';
6 +import { Menu, X } from 'lucide-react';
7 +import type { NavItem } from '@/config/nav';
8 +
9 +export function MobileNav({ items, groups }: { items: NavItem[]; groups: Array<{ id: NavItem['group']; label: string }> }) {
10 + const [open, setOpen] = useState(false);
11 + const pathname = usePathname();
12 + useEffect(() => setOpen(false), [pathname]);
13 + useEffect(() => {
14 + document.body.style.overflow = open ? 'hidden' : '';
15 + return () => {
16 + document.body.style.overflow = '';
17 + };
18 + }, [open]);
19 + return (
20 + <div className="lg:hidden">
21 + <button type="button" aria-label="Open menu" aria-expanded={open} className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted hover:bg-inset hover:text-fg" onClick={() => setOpen(true)}>
22 + <Menu className="h-5 w-5" />
23 + </button>
24 + {open ? (
25 + <div className="fixed inset-0 z-50 flex" role="dialog" aria-modal="true">
26 + <div className="absolute inset-0 bg-black/40" onClick={() => setOpen(false)} />
27 + <div className="relative flex h-[100dvh] w-[86vw] max-w-sm flex-col overflow-y-auto bg-elevated shadow-pop safe-bottom">
28 + <div className="flex h-14 items-center justify-between border-b border-border px-4">
29 + <span className="text-sm font-semibold">Menu</span>
30 + <button type="button" aria-label="Close menu" className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted hover:bg-inset" onClick={() => setOpen(false)}>
31 + <X className="h-5 w-5" />
32 + </button>
33 + </div>
34 + <nav className="flex-1 px-2 py-3">
35 + {groups.map((g) => (
36 + <div key={g.id} className="mb-3">
37 + <p className="px-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-subtle">{g.label}</p>
38 + {items
39 + .filter((n) => n.group === g.id)
40 + .map((n) => (
41 + <Link key={n.href} href={n.href} className={`block rounded-md px-2 py-2 text-[15px] ${pathname === n.href ? 'bg-inset font-medium text-fg' : 'text-muted'}`}>
42 + {n.label}
43 + </Link>
44 + ))}
45 + </div>
46 + ))}
47 + <Link href="/account" className="mt-2 block rounded-md border border-border px-3 py-2 text-center text-sm font-medium">
48 + Sign in
49 + </Link>
50 + </nav>
51 + </div>
52 + </div>
53 + ) : null}
54 + </div>
55 + );
56 +}
added apps/web/src/components/layout/site-footer.tsx +42 −0
@@ -0,0 +1,42 @@
1 +import Link from 'next/link';
2 +import { NAV, NAV_GROUPS } from '@/config/nav';
3 +import { Logo } from './site-header';
4 +
5 +export function SiteFooter() {
6 + return (
7 + <footer className="mt-16 border-t border-border bg-sunken">
8 + <div className="mx-auto max-w-[1440px] px-4 py-10 sm:px-6">
9 + <div className="grid grid-cols-2 gap-8 md:grid-cols-6">
10 + <div className="col-span-2 md:col-span-2">
11 + <Logo />
12 + <p className="mt-3 max-w-xs text-xs leading-relaxed text-muted">The global data layer for collectible assets: prices, sales, listings, rarity, liquidity and indices across marketplaces and auction houses.</p>
13 + </div>
14 + {NAV_GROUPS.filter((g) => g.id !== 'discover').map((g) => (
15 + <div key={g.id}>
16 + <p className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-subtle">{g.label}</p>
17 + <ul className="space-y-1">
18 + {NAV.filter((n) => n.group === g.id).map((n) => (
19 + <li key={n.href}>
20 + <Link href={n.href} className="text-xs text-muted hover:text-fg">
21 + {n.label}
22 + </Link>
23 + </li>
24 + ))}
25 + </ul>
26 + </div>
27 + ))}
28 + </div>
29 + <div className="mt-8 border-t border-border pt-5 text-[11px] leading-relaxed text-subtle">
30 + <p>
31 + RareIndex is an information platform. Valuations are estimates derived from observed public data and carry a confidence level; listing prices are not confirmed transactions; past performance does not guarantee future results. RareIndex does not authenticate items. Third-party names and marks belong to their owners; data is attributed to its source.{' '}
32 + <Link href="/about#disclaimers" className="underline hover:text-fg">
33 + Read the full disclaimers
34 + </Link>
35 + .
36 + </p>
37 + <p className="mt-2">© {new Date().getFullYear()} RareIndex.io</p>
38 + </div>
39 + </div>
40 + </footer>
41 + );
42 +}
added apps/web/src/components/layout/site-header.tsx +60 −0
@@ -0,0 +1,60 @@
1 +import Link from 'next/link';
2 +import { NAV, NAV_GROUPS, PRIMARY_NAV } from '@/config/nav';
3 +import { ThemeToggle } from './theme-toggle';
4 +import { MobileNav } from './mobile-nav';
5 +import { HeaderSearch } from './header-search';
6 +
7 +export function Logo({ className }: { className?: string }) {
8 + return (
9 + <Link href="/" className={`flex items-center gap-2 ${className ?? ''}`} aria-label="RareIndex home">
10 + <span className="inline-flex h-6 w-6 items-center justify-center rounded-sm bg-accent text-[11px] font-bold text-accent-fg">R</span>
11 + <span className="text-[15px] font-semibold tracking-tight text-fg">
12 + Rare<span className="text-muted">Index</span>
13 + </span>
14 + </Link>
15 + );
16 +}
17 +
18 +export function SiteHeader() {
19 + return (
20 + <header className="sticky top-0 z-40 border-b border-border bg-bg/85 backdrop-blur supports-[backdrop-filter]:bg-bg/70">
21 + <div className="mx-auto flex h-14 max-w-[1440px] items-center gap-3 px-4 sm:px-6">
22 + <MobileNav items={NAV} groups={NAV_GROUPS} />
23 + <Logo />
24 + <nav className="ml-4 hidden items-center gap-1 lg:flex" aria-label="Primary">
25 + {PRIMARY_NAV.map((n) => (
26 + <Link key={n.href} href={n.href} className="rounded-md px-2.5 py-1.5 text-[13px] font-medium text-muted hover:bg-inset hover:text-fg">
27 + {n.label}
28 + </Link>
29 + ))}
30 + <details className="group relative">
31 + <summary className="cursor-pointer list-none rounded-md px-2.5 py-1.5 text-[13px] font-medium text-muted hover:bg-inset hover:text-fg [&::-webkit-details-marker]:hidden">More</summary>
32 + <div className="absolute left-0 top-full z-50 mt-1 grid w-[640px] grid-cols-3 gap-4 rounded-lg border border-border bg-elevated p-4 shadow-pop">
33 + {NAV_GROUPS.map((g) => (
34 + <div key={g.id}>
35 + <p className="mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-subtle">{g.label}</p>
36 + <ul className="space-y-0.5">
37 + {NAV.filter((n) => n.group === g.id).map((n) => (
38 + <li key={n.href}>
39 + <Link href={n.href} className="block rounded-sm px-1.5 py-1 text-[13px] text-muted hover:bg-inset hover:text-fg">
40 + {n.label}
41 + </Link>
42 + </li>
43 + ))}
44 + </ul>
45 + </div>
46 + ))}
47 + </div>
48 + </details>
49 + </nav>
50 + <div className="ml-auto flex items-center gap-1.5">
51 + <HeaderSearch />
52 + <ThemeToggle />
53 + <Link href="/account" className="hidden rounded-md border border-border px-2.5 py-1.5 text-[13px] font-medium text-fg hover:bg-inset sm:inline-flex">
54 + Sign in
55 + </Link>
56 + </div>
57 + </div>
58 + </header>
59 + );
60 +}
added apps/web/src/components/layout/theme-toggle.tsx +41 −0
@@ -0,0 +1,41 @@
1 +'use client';
2 +
3 +import { useEffect, useState } from 'react';
4 +import { Moon, Sun } from 'lucide-react';
5 +
6 +type Theme = 'light' | 'dark';
7 +
8 +function applyTheme(t: Theme) {
9 + document.documentElement.setAttribute('data-theme', t);
10 + try {
11 + localStorage.setItem('ri-theme', t);
12 + } catch {
13 + /* ignore */
14 + }
15 +}
16 +
17 +export function ThemeToggle({ className }: { className?: string }) {
18 + const [theme, setTheme] = useState<Theme>('light');
19 + useEffect(() => {
20 + const attr = document.documentElement.getAttribute('data-theme') as Theme | null;
21 + if (attr) setTheme(attr);
22 + else setTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
23 + }, []);
24 + return (
25 + <button
26 + type="button"
27 + aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
28 + className={className ?? 'inline-flex h-8 w-8 items-center justify-center rounded-md text-muted hover:bg-inset hover:text-fg'}
29 + onClick={() => {
30 + const next: Theme = theme === 'dark' ? 'light' : 'dark';
31 + setTheme(next);
32 + applyTheme(next);
33 + }}
34 + >
35 + {theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
36 + </button>
37 + );
38 +}
39 +
40 +/** Inline script: apply the stored theme before paint to avoid flashes. */
41 +export const THEME_SCRIPT = `(function(){try{var t=localStorage.getItem('ri-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();`;
added apps/web/src/components/ui/primitives.tsx +91 −0
@@ -0,0 +1,91 @@
1 +import Link from 'next/link';
2 +import type { ReactNode } from 'react';
3 +import { cn, deltaClass, fmtPct } from '@/lib/format';
4 +
5 +/** Minimal, dense UI primitives. Keep them boring; the data is the design. */
6 +
7 +export function Card({ children, className, as: Tag = 'section' }: { children: ReactNode; className?: string; as?: 'section' | 'div' | 'article' }) {
8 + return <Tag className={cn('card', className)}>{children}</Tag>;
9 +}
10 +
11 +export function CardHeader({ title, subtitle, action, className }: { title: ReactNode; subtitle?: ReactNode; action?: ReactNode; className?: string }) {
12 + return (
13 + <header className={cn('flex items-start justify-between gap-3 border-b border-border px-4 py-3', className)}>
14 + <div className="min-w-0">
15 + <h2 className="text-sm font-semibold tracking-tight text-fg">{title}</h2>
16 + {subtitle ? <p className="mt-0.5 text-xs text-muted">{subtitle}</p> : null}
17 + </div>
18 + {action ? <div className="shrink-0 text-xs">{action}</div> : null}
19 + </header>
20 + );
21 +}
22 +
23 +export function Delta({ value, digits = 2, className }: { value: number | null | undefined; digits?: number; className?: string }) {
24 + return <span className={cn('num font-medium', deltaClass(value), className)}>{fmtPct(value, digits)}</span>;
25 +}
26 +
27 +export function Badge({ children, tone = 'neutral', className }: { children: ReactNode; tone?: 'neutral' | 'gain' | 'loss' | 'index' | 'rarity' | 'alert' | 'gold'; className?: string }) {
28 + const tones: Record<string, string> = {
29 + neutral: 'bg-inset text-muted',
30 + gain: 'bg-gain-bg text-gain',
31 + loss: 'bg-loss-bg text-loss',
32 + index: 'bg-index-bg text-index',
33 + rarity: 'bg-rarity-bg text-rarity',
34 + alert: 'bg-alert-bg text-alert',
35 + gold: 'bg-alert-bg text-gold',
36 + };
37 + return <span className={cn('inline-flex items-center rounded-sm px-1.5 py-0.5 text-[11px] font-medium leading-4 tracking-wide', tones[tone], className)}>{children}</span>;
38 +}
39 +
40 +export function Stat({ label, value, sub, className }: { label: ReactNode; value: ReactNode; sub?: ReactNode; className?: string }) {
41 + return (
42 + <div className={cn('flex flex-col gap-0.5', className)}>
43 + <span className="text-[11px] font-medium uppercase tracking-wider text-subtle">{label}</span>
44 + <span className="num text-lg font-semibold leading-tight text-fg">{value}</span>
45 + {sub ? <span className="text-xs text-muted">{sub}</span> : null}
46 + </div>
47 + );
48 +}
49 +
50 +export function EmptyState({ title, description, action, className }: { title: ReactNode; description?: ReactNode; action?: ReactNode; className?: string }) {
51 + return (
52 + <div className={cn('flex flex-col items-center justify-center gap-2 px-6 py-12 text-center', className)}>
53 + <p className="text-sm font-medium text-fg">{title}</p>
54 + {description ? <p className="max-w-md text-xs leading-relaxed text-muted">{description}</p> : null}
55 + {action ? <div className="mt-2">{action}</div> : null}
56 + </div>
57 + );
58 +}
59 +
60 +/** Honest placeholder for metrics without enough evidence (§191–§192). */
61 +export function Unavailable({ reason = 'Data unavailable', className }: { reason?: string; className?: string }) {
62 + return <span className={cn('text-subtle', className)} title={reason}>—</span>;
63 +}
64 +
65 +export function SectionTitle({ children, href, hrefLabel = 'View all', className }: { children: ReactNode; href?: string; hrefLabel?: string; className?: string }) {
66 + return (
67 + <div className={cn('mb-3 flex items-baseline justify-between gap-3', className)}>
68 + <h2 className="text-base font-semibold tracking-tight text-fg">{children}</h2>
69 + {href ? (
70 + <Link href={href} className="text-xs font-medium text-muted hover:text-fg">
71 + {hrefLabel} →
72 + </Link>
73 + ) : null}
74 + </div>
75 + );
76 +}
77 +
78 +export function Skeleton({ className }: { className?: string }) {
79 + return <div className={cn('animate-pulse rounded-sm bg-inset', className)} aria-hidden />;
80 +}
81 +
82 +export function Table({ children, className, dense = true }: { children: ReactNode; className?: string; dense?: boolean }) {
83 + return (
84 + <div className={cn('w-full overflow-x-auto scrollbar-none', className)}>
85 + <table className={cn('w-full border-collapse text-left', dense ? 'text-[13px]' : 'text-sm')}>{children}</table>
86 + </div>
87 + );
88 +}
89 +export const th = 'sticky top-0 z-[1] whitespace-nowrap border-b border-border bg-elevated px-3 py-2 text-[11px] font-medium uppercase tracking-wider text-subtle';
90 +export const td = 'whitespace-nowrap border-b border-border px-3 py-2 align-middle';
91 +export const tdNum = `${td} num text-right`;
added apps/web/src/config/nav.ts +47 −0
@@ -0,0 +1,47 @@
1 +/**
2 + * Main navigation (CLAUDE.md §2). Single source of truth for header, mobile menu, footer and sitemap.
3 + * Routes are grouped so the header stays legible; every module remains reachable.
4 + */
5 +export interface NavItem {
6 + label: string;
7 + href: string;
8 + description: string;
9 + group: 'discover' | 'markets' | 'portfolio' | 'tools' | 'company';
10 + /** show in the primary header bar on desktop */
11 + primary?: boolean;
12 +}
13 +
14 +export const NAV: NavItem[] = [
15 + { label: 'Home', href: '/', description: 'The global market for collectibles', group: 'discover' },
16 + { label: 'Explore', href: '/explore', description: 'Browse tracked assets across every category', group: 'discover', primary: true },
17 + { label: 'Markets', href: '/markets', description: 'Category markets, movers and volume', group: 'markets', primary: true },
18 + { label: 'Categories', href: '/categories', description: 'The RareIndex taxonomy', group: 'discover' },
19 + { label: 'RareIndex', href: '/rareindex', description: 'RARE and its subindices', group: 'markets', primary: true },
20 + { label: 'Trending', href: '/trending', description: 'What collectors are moving into', group: 'markets' },
21 + { label: 'Sales', href: '/sales', description: 'Latest observed transactions', group: 'markets', primary: true },
22 + { label: 'Listings', href: '/listings', description: 'Live asks across marketplaces', group: 'markets' },
23 + { label: 'Auctions', href: '/auctions', description: 'Lots, results and auction houses', group: 'markets' },
24 + { label: 'Price History', href: '/price-history', description: 'Historical series for any asset', group: 'tools' },
25 + { label: 'Collections', href: '/collections', description: 'Track your portfolio', group: 'portfolio', primary: true },
26 + { label: 'Watchlist', href: '/watchlist', description: 'Assets, sets and markets you follow', group: 'portfolio' },
27 + { label: 'Compare', href: '/compare', description: 'Compare assets and categories side by side', group: 'tools' },
28 + { label: 'Scanner', href: '/scanner', description: 'Identify and price from a photo or URL', group: 'tools', primary: true },
29 + { label: 'AI Research', href: '/research', description: 'Ask questions over structured market data', group: 'tools' },
30 + { label: 'Market Map', href: '/market-map', description: 'Heatmap of category performance', group: 'markets' },
31 + { label: 'Grading', href: '/grading', description: 'Graders, populations and premiums', group: 'tools' },
32 + { label: 'Auction Calendar', href: '/auctions/calendar', description: 'Upcoming auctions worldwide', group: 'markets' },
33 + { label: 'News', href: '/news', description: 'Market news and record sales', group: 'discover' },
34 + { label: 'Data', href: '/data', description: 'Methodology, coverage and exports', group: 'company' },
35 + { label: 'API', href: '/api-docs', description: 'Programmatic access to RareIndex data', group: 'company' },
36 + { label: 'About', href: '/about', description: 'Mission, disclaimers and contact', group: 'company' },
37 +];
38 +
39 +export const NAV_GROUPS: Array<{ id: NavItem['group']; label: string }> = [
40 + { id: 'discover', label: 'Discover' },
41 + { id: 'markets', label: 'Markets' },
42 + { id: 'portfolio', label: 'Portfolio' },
43 + { id: 'tools', label: 'Tools' },
44 + { id: 'company', label: 'Company' },
45 +];
46 +
47 +export const PRIMARY_NAV = NAV.filter((n) => n.primary);
added apps/web/src/lib/db.ts +6 −0
@@ -0,0 +1,6 @@
1 +import 'server-only';
2 +import { getDb } from '@rareindex/database';
3 +
4 +/** Server-side database handle for server components, route handlers and server actions. */
5 +export const db = () => getDb();
6 +export * from '@rareindex/database';
added apps/web/src/lib/format.ts +81 −0
@@ -0,0 +1,81 @@
1 +import type { CurrencyCode } from '@rareindex/shared';
2 +
3 +/** Display formatting shared by server and client components (no Node-only imports). */
4 +
5 +export function fmtMoney(amount: number | null | undefined, currency: CurrencyCode | string = 'USD', opts: { compact?: boolean; digits?: number } = {}): string {
6 + if (amount === null || amount === undefined || !Number.isFinite(amount)) return '—';
7 + const zero = currency === 'JPY' || currency === 'KRW';
8 + const digits = opts.digits ?? (zero ? 0 : Math.abs(amount) >= 1000 ? 0 : 2);
9 + try {
10 + return new Intl.NumberFormat('en-US', { style: 'currency', currency, notation: opts.compact ? 'compact' : 'standard', maximumFractionDigits: digits, minimumFractionDigits: opts.compact ? 0 : digits }).format(amount);
11 + } catch {
12 + return `${amount.toFixed(digits)} ${currency}`;
13 + }
14 +}
15 +
16 +export function fmtPct(value: number | null | undefined, digits = 2, signed = true): string {
17 + if (value === null || value === undefined || !Number.isFinite(value)) return '—';
18 + const sign = signed && value > 0 ? '+' : '';
19 + return `${sign}${(value * 100).toFixed(digits)}%`;
20 +}
21 +
22 +export function fmtNum(value: number | null | undefined, opts: { compact?: boolean; digits?: number } = {}): string {
23 + if (value === null || value === undefined || !Number.isFinite(value)) return '—';
24 + return new Intl.NumberFormat('en-US', { notation: opts.compact ? 'compact' : 'standard', maximumFractionDigits: opts.digits ?? (opts.compact ? 1 : 0) }).format(value);
25 +}
26 +
27 +export function fmtDate(d: Date | string | null | undefined, opts: { month?: 'short' | 'long' | 'numeric'; year?: boolean; time?: boolean } = {}): string {
28 + if (!d) return '—';
29 + const date = typeof d === 'string' ? new Date(d.length === 10 ? `${d}T00:00:00Z` : d) : d;
30 + if (Number.isNaN(date.getTime())) return '—';
31 + return new Intl.DateTimeFormat('en-US', { month: opts.month ?? 'short', day: 'numeric', year: opts.year === false ? undefined : 'numeric', ...(opts.time ? { hour: 'numeric', minute: '2-digit' } : {}), timeZone: 'UTC' }).format(date);
32 +}
33 +
34 +export function fmtRelative(d: Date | string | null | undefined): string {
35 + if (!d) return '—';
36 + const date = typeof d === 'string' ? new Date(d) : d;
37 + const diff = Date.now() - date.getTime();
38 + const abs = Math.abs(diff);
39 + const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
40 + const units: Array<[number, Intl.RelativeTimeFormatUnit]> = [
41 + [60_000, 'second'],
42 + [3_600_000, 'minute'],
43 + [86_400_000, 'hour'],
44 + [604_800_000, 'day'],
45 + [2_629_800_000, 'week'],
46 + [31_557_600_000, 'month'],
47 + [Infinity, 'year'],
48 + ];
49 + let prevMs = 1;
50 + for (const [ms, unit] of units) {
51 + if (abs < ms) return rtf.format(Math.round(-diff / prevMs), unit);
52 + prevMs = ms;
53 + }
54 + return fmtDate(date);
55 +}
56 +
57 +export function deltaClass(value: number | null | undefined): string {
58 + if (value === null || value === undefined || !Number.isFinite(value) || Math.abs(value) < 0.00005) return 'text-flat';
59 + return value > 0 ? 'text-gain' : 'text-loss';
60 +}
61 +
62 +export function confidenceLabel(c: number | null | undefined): 'High' | 'Medium' | 'Low' | 'Insufficient' {
63 + if (c === null || c === undefined) return 'Insufficient';
64 + if (c >= 0.75) return 'High';
65 + if (c >= 0.5) return 'Medium';
66 + if (c > 0) return 'Low';
67 + return 'Insufficient';
68 +}
69 +
70 +export function scoreLabel(s: number | null | undefined): string {
71 + if (s === null || s === undefined) return 'n/a';
72 + if (s >= 80) return 'Very high';
73 + if (s >= 60) return 'High';
74 + if (s >= 40) return 'Moderate';
75 + if (s >= 20) return 'Low';
76 + return 'Very low';
77 +}
78 +
79 +export function cn(...parts: Array<string | false | null | undefined>): string {
80 + return parts.filter(Boolean).join(' ');
81 +}
added apps/web/tsconfig.json +22 −0
@@ -0,0 +1,22 @@
1 +{
2 + "compilerOptions": {
3 + "target": "ES2022",
4 + "lib": ["dom", "dom.iterable", "esnext"],
5 + "allowJs": true,
6 + "skipLibCheck": true,
7 + "strict": true,
8 + "noUncheckedIndexedAccess": true,
9 + "noEmit": true,
10 + "esModuleInterop": true,
11 + "module": "esnext",
12 + "moduleResolution": "bundler",
13 + "resolveJsonModule": true,
14 + "isolatedModules": true,
15 + "jsx": "react-jsx",
16 + "incremental": true,
17 + "plugins": [{ "name": "next" }],
18 + "paths": { "@/*": ["./src/*"] }
19 + },
20 + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"],
21 + "exclude": ["node_modules"]
22 +}
added apps/web/vitest.config.ts +7 −0
@@ -0,0 +1,7 @@
1 +import { defineConfig } from 'vitest/config';
2 +import path from 'node:path';
3 +
4 +export default defineConfig({
5 + resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
6 + test: { include: ['src/**/*.test.ts'] },
7 +});
added connectors/README.md +18 −0
@@ -0,0 +1,18 @@
1 +# Source connectors
2 +
3 +Every source lives in `connectors/<engine>/<source-id>/index.ts` and is declared in `registry.json` (§106).
4 +Adding a marketplace requires only (§197):
5 +
6 +1. **metadata** — an entry in `registry.json` (`module` points to the folder, e.g. `"api/scryfall"`);
7 +2. **extraction schema** — the payload shape the connector captures in `crawl()` (typed with Zod);
8 +3. **normalization adapter** — a pure `normalize(raw)` returning canonical `NormalizedRecord[]`;
9 +4. **fixtures** — at least one real captured payload in `data/fixtures/<source-id>/*.json` and a
10 + `*.test.ts` next to the connector calling `runFixtureSuite`.
11 +
12 +The generic pipeline (raw store → normalizer → entity resolution → dedupe → validation → canonical
13 +tables → valuation → indices) then works without changes.
14 +
15 +Rules: never invent data, keep source quirks inside the connector, store the source's own dates,
16 +keep the native currency, respect robots/terms/rate limits, and never bypass access controls (§179).
17 +
18 +Module contract: `export default (meta: ConnectorMeta) => RareIndexConnector`.
added connectors/package.json +21 −0
@@ -0,0 +1,21 @@
1 +{
2 + "name": "@rareindex/source-connectors",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "scripts": {
7 + "typecheck": "tsc -p tsconfig.json --noEmit",
8 + "test": "vitest run --passWithNoTests"
9 + },
10 + "dependencies": {
11 + "@rareindex/connectors": "workspace:*",
12 + "@rareindex/shared": "workspace:*",
13 + "@rareindex/taxonomy": "workspace:*",
14 + "zod": "^4.0.0"
15 + },
16 + "devDependencies": {
17 + "@types/node": "^24.0.0",
18 + "typescript": "^5.9.3",
19 + "vitest": "^3.2.0"
20 + }
21 +}
added connectors/registry.json +4 −0
@@ -0,0 +1,4 @@
1 +{
2 + "version": "1.0",
3 + "connectors": []
4 +}
added connectors/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": ".",
5 + "noEmit": true
6 + },
7 + "include": ["./**/*.ts"],
8 + "exclude": ["node_modules"]
9 +}
added connectors/vitest.config.ts +5 −0
@@ -0,0 +1,5 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: { include: ['**/*.test.ts'], exclude: ['node_modules/**'], testTimeout: 30_000 },
5 +});
added data/taxonomy/categories.json +160 −0
@@ -0,0 +1,160 @@
1 +{
2 + "$comment": "Extensible RareIndex taxonomy. Families (level 0) map to CLAUDE.md §5–§100; children are seeded subcategories. New nodes may be added at runtime via approved taxonomy_proposals. phase: 1 = launch wedge, 2 = next, 3 = later. Never hardcode this list in application code.",
3 + "nodes": [
4 + { "slug": "trading_cards", "name": "Trading Cards", "short": "TCG", "phase": 1, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc", "sgc", "tag", "ace"], "icon": "cards" },
5 + { "slug": "pokemon", "parent": "trading_cards", "name": "Pokémon", "phase": 1, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc", "sgc", "tag", "ace"] },
6 + { "slug": "magic_the_gathering", "parent": "trading_cards", "name": "Magic: The Gathering", "short": "MTG", "phase": 1, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc"] },
7 + { "slug": "yugioh", "parent": "trading_cards", "name": "Yu-Gi-Oh!", "phase": 1, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc", "ace"] },
8 + { "slug": "one_piece_card_game", "parent": "trading_cards", "name": "One Piece Card Game", "phase": 2, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc"] },
9 + { "slug": "disney_lorcana", "parent": "trading_cards", "name": "Disney Lorcana", "phase": 2, "index": "RARE-TCG", "conditionScale": "cards", "graders": ["psa", "bgs", "cgc"] },
10 + { "slug": "dragon_ball_tcg", "parent": "trading_cards", "name": "Dragon Ball Super / Masters", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
11 + { "slug": "digimon_tcg", "parent": "trading_cards", "name": "Digimon Card Game", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
12 + { "slug": "flesh_and_blood", "parent": "trading_cards", "name": "Flesh and Blood", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
13 + { "slug": "weiss_schwarz", "parent": "trading_cards", "name": "Weiß Schwarz", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
14 + { "slug": "final_fantasy_tcg", "parent": "trading_cards", "name": "Final Fantasy TCG", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
15 + { "slug": "star_wars_tcg", "parent": "trading_cards", "name": "Star Wars (Unlimited, CCG, Destiny)", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards" },
16 + { "slug": "other_tcg", "parent": "trading_cards", "name": "Other TCGs", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards", "description": "Naruto, My Hero Academia, UniVersus, MetaZoo, Cardfight Vanguard, Battle Spirits, Force of Will, KeyForge, Chaotic, Duel Masters, Altered, Sorcery, Riftbound, Grand Archive, Gundam Card Game" },
17 + { "slug": "non_sport_cards", "parent": "trading_cards", "name": "Non-Sport & Entertainment Cards", "phase": 3, "index": "RARE-TCG", "conditionScale": "cards", "description": "Marvel, DC, Garbage Pail Kids, Topps entertainment" },
18 +
19 + { "slug": "sports_cards", "name": "Sports Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards", "graders": ["psa", "bgs", "sgc", "cgc"], "icon": "sports" },
20 + { "slug": "baseball_cards", "parent": "sports_cards", "name": "Baseball Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards" },
21 + { "slug": "basketball_cards", "parent": "sports_cards", "name": "Basketball Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards" },
22 + { "slug": "football_cards", "parent": "sports_cards", "name": "Football Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards" },
23 + { "slug": "hockey_cards", "parent": "sports_cards", "name": "Hockey Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards" },
24 + { "slug": "soccer_cards", "parent": "sports_cards", "name": "Soccer Cards", "phase": 1, "index": "RARE-SPORT", "conditionScale": "cards" },
25 + { "slug": "f1_cards", "parent": "sports_cards", "name": "Formula 1 Cards", "phase": 2, "index": "RARE-SPORT", "conditionScale": "cards" },
26 + { "slug": "other_sports_cards", "parent": "sports_cards", "name": "Other Sports Cards", "phase": 2, "index": "RARE-SPORT", "conditionScale": "cards", "description": "UFC, boxing, golf, tennis, cricket, wrestling, NASCAR, Olympics, lacrosse, rugby" },
27 +
28 + { "slug": "comics", "name": "Comics", "phase": 1, "index": "RARE-COMIC", "conditionScale": "comics", "graders": ["cgc", "cbcs", "pgx"], "icon": "comics" },
29 + { "slug": "marvel_comics", "parent": "comics", "name": "Marvel", "phase": 1, "index": "RARE-COMIC", "conditionScale": "comics" },
30 + { "slug": "dc_comics", "parent": "comics", "name": "DC", "phase": 1, "index": "RARE-COMIC", "conditionScale": "comics" },
31 + { "slug": "independent_comics", "parent": "comics", "name": "Independent & Golden Age", "phase": 2, "index": "RARE-COMIC", "conditionScale": "comics", "description": "Image, Dark Horse, IDW, Boom, Archie, Valiant, Dynamite, EC" },
32 + { "slug": "manga", "name": "Manga", "phase": 2, "index": "RARE-BOOK", "conditionScale": "books", "graders": ["cgc"], "icon": "book" },
33 +
34 + { "slug": "video_games", "name": "Video Games", "phase": 1, "index": "RARE-GAME", "conditionScale": "video_games", "graders": ["wata", "vga", "cgc"], "icon": "game" },
35 + { "slug": "nintendo_games", "parent": "video_games", "name": "Nintendo", "phase": 1, "index": "RARE-GAME", "conditionScale": "video_games", "description": "NES, Famicom, SNES, N64, GameCube, Wii, Switch, Game Boy family, DS, 3DS" },
36 + { "slug": "sega_games", "parent": "video_games", "name": "Sega", "phase": 1, "index": "RARE-GAME", "conditionScale": "video_games" },
37 + { "slug": "playstation_games", "parent": "video_games", "name": "PlayStation", "phase": 1, "index": "RARE-GAME", "conditionScale": "video_games" },
38 + { "slug": "xbox_games", "parent": "video_games", "name": "Xbox", "phase": 1, "index": "RARE-GAME", "conditionScale": "video_games" },
39 + { "slug": "atari_retro_games", "parent": "video_games", "name": "Atari & Early Consoles", "phase": 2, "index": "RARE-GAME", "conditionScale": "video_games", "description": "Atari, Intellivision, ColecoVision, Neo Geo, PC Engine/TurboGrafx, MSX, Commodore, Amiga" },
40 + { "slug": "pc_games", "parent": "video_games", "name": "PC Games", "phase": 2, "index": "RARE-GAME", "conditionScale": "video_games" },
41 + { "slug": "gaming_hardware", "name": "Gaming Hardware", "phase": 2, "index": "RARE-GAME", "conditionScale": "electronics", "icon": "console" },
42 + { "slug": "arcade_pinball", "name": "Arcade & Pinball", "phase": 3, "index": "RARE-GAME", "conditionScale": "machines" },
43 +
44 + { "slug": "sneakers", "name": "Sneakers", "phase": 1, "index": "RARE-SNEAKER", "conditionScale": "sneakers", "icon": "sneaker" },
45 + { "slug": "nike_jordan", "parent": "sneakers", "name": "Nike & Jordan", "phase": 1, "index": "RARE-SNEAKER", "conditionScale": "sneakers" },
46 + { "slug": "adidas_yeezy", "parent": "sneakers", "name": "adidas & Yeezy", "phase": 1, "index": "RARE-SNEAKER", "conditionScale": "sneakers" },
47 + { "slug": "new_balance_asics_other", "parent": "sneakers", "name": "New Balance, ASICS & Others", "phase": 1, "index": "RARE-SNEAKER", "conditionScale": "sneakers" },
48 +
49 + { "slug": "watches", "name": "Watches", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches", "icon": "watch" },
50 + { "slug": "rolex", "parent": "watches", "name": "Rolex", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches" },
51 + { "slug": "patek_philippe", "parent": "watches", "name": "Patek Philippe", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches" },
52 + { "slug": "audemars_piguet", "parent": "watches", "name": "Audemars Piguet", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches" },
53 + { "slug": "omega", "parent": "watches", "name": "Omega", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches" },
54 + { "slug": "other_watches", "parent": "watches", "name": "Other Watch Brands", "phase": 1, "index": "RARE-WATCH", "conditionScale": "watches", "description": "Richard Mille, Vacheron Constantin, Cartier, Tudor, Breitling, IWC, JLC, A. Lange & Söhne, Breguet, Blancpain, Zenith, Grand Seiko, Seiko, TAG Heuer, Panerai, Hublot, F.P. Journe, MB&F, De Bethune, Greubel Forsey, Urwerk" },
55 +
56 + { "slug": "lego", "name": "LEGO", "phase": 1, "index": "RARE-LEGO", "conditionScale": "lego", "icon": "brick" },
57 + { "slug": "lego_sets", "parent": "lego", "name": "LEGO Sets", "phase": 1, "index": "RARE-LEGO", "conditionScale": "lego" },
58 + { "slug": "lego_minifigures", "parent": "lego", "name": "LEGO Minifigures", "phase": 1, "index": "RARE-LEGO", "conditionScale": "lego" },
59 +
60 + { "slug": "funko", "name": "Funko", "phase": 1, "index": "RARE-TOY", "conditionScale": "boxed_toys", "icon": "toy" },
61 + { "slug": "designer_toys", "name": "Designer Toys", "phase": 1, "index": "RARE-TOY", "conditionScale": "boxed_toys", "description": "Bearbrick, Medicom, KAWS, Kidrobot, Superplastic, Mighty Jaxx, Pop Mart (Labubu, Molly, Skullpanda, Dimoo), Murakami" },
62 + { "slug": "action_figures", "name": "Action Figures", "phase": 2, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
63 + { "slug": "vintage_toys", "name": "Vintage Toys", "phase": 2, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
64 + { "slug": "dolls", "name": "Dolls", "phase": 3, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
65 + { "slug": "plush", "name": "Plush", "phase": 3, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
66 + { "slug": "model_cars", "name": "Model Cars", "phase": 2, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
67 + { "slug": "model_trains", "name": "Model Trains", "phase": 3, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
68 +
69 + { "slug": "automobiles", "name": "Automobiles", "phase": 2, "index": "RARE-AUTO", "conditionScale": "vehicles", "icon": "car" },
70 + { "slug": "motorcycles", "name": "Motorcycles", "phase": 3, "index": "RARE-AUTO", "conditionScale": "vehicles" },
71 + { "slug": "automotive_memorabilia", "name": "Automotive Memorabilia", "phase": 3, "index": "RARE-AUTO", "conditionScale": "general" },
72 + { "slug": "formula_1", "name": "Formula 1 Memorabilia", "phase": 3, "index": "RARE-AUTO", "conditionScale": "general" },
73 + { "slug": "sports_memorabilia", "name": "Sports Memorabilia", "phase": 2, "index": "RARE-SPORT", "conditionScale": "general" },
74 +
75 + { "slug": "coins", "name": "Coins", "phase": 2, "index": "RARE-COIN", "conditionScale": "coins", "graders": ["pcgs", "ngc", "anacs", "iccs"], "icon": "coin" },
76 + { "slug": "banknotes", "name": "Banknotes", "phase": 3, "index": "RARE-COIN", "conditionScale": "coins", "graders": ["pmg", "pcgs"] },
77 + { "slug": "stamps", "name": "Stamps", "phase": 3, "index": "RARE-COIN", "conditionScale": "stamps" },
78 +
79 + { "slug": "books", "name": "Books", "phase": 2, "index": "RARE-BOOK", "conditionScale": "books", "icon": "book" },
80 + { "slug": "historical_documents", "name": "Historical Documents", "phase": 3, "index": "RARE-BOOK", "conditionScale": "books" },
81 + { "slug": "autographs", "name": "Autographs", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general", "graders": ["psa_dna", "jsa", "beckett_auth"] },
82 + { "slug": "maps", "name": "Maps & Globes", "phase": 3, "index": "RARE-BOOK", "conditionScale": "books" },
83 + { "slug": "postcards", "name": "Postcards", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general" },
84 +
85 + { "slug": "music", "name": "Music (Vinyl & Recordings)", "phase": 2, "index": "RARE-MUSIC", "conditionScale": "records", "icon": "vinyl" },
86 + { "slug": "music_memorabilia", "name": "Music Memorabilia", "phase": 3, "index": "RARE-MUSIC", "conditionScale": "general" },
87 + { "slug": "musical_instruments", "name": "Musical Instruments", "phase": 3, "index": "RARE-MUSIC", "conditionScale": "general" },
88 +
89 + { "slug": "movie_memorabilia", "name": "Movie Memorabilia & Props", "phase": 3, "index": "RARE-ART", "conditionScale": "general" },
90 + { "slug": "movie_posters", "name": "Movie Posters", "phase": 3, "index": "RARE-ART", "conditionScale": "paper" },
91 + { "slug": "animation_art", "name": "Animation Art", "phase": 3, "index": "RARE-ART", "conditionScale": "paper" },
92 + { "slug": "art", "name": "Art", "phase": 2, "index": "RARE-ART", "conditionScale": "art", "icon": "art" },
93 + { "slug": "contemporary_art", "parent": "art", "name": "Contemporary Art & Editions", "phase": 2, "index": "RARE-ART", "conditionScale": "art" },
94 + { "slug": "photography", "name": "Photography", "phase": 3, "index": "RARE-ART", "conditionScale": "paper" },
95 + { "slug": "cameras", "name": "Cameras", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
96 +
97 + { "slug": "apple_collectibles", "name": "Apple Collectibles", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics", "icon": "tech" },
98 + { "slug": "vintage_computers", "name": "Vintage Computers", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
99 + { "slug": "computer_hardware", "name": "Computer Hardware", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
100 + { "slug": "mobile_phones", "name": "Mobile Phones", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
101 + { "slug": "keyboards", "name": "Keyboards", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
102 + { "slug": "audio_equipment", "name": "Audio Equipment", "phase": 3, "index": "RARE-TECH", "conditionScale": "electronics" },
103 +
104 + { "slug": "wine", "name": "Wine", "phase": 2, "index": "RARE-WINE", "conditionScale": "bottles", "icon": "wine" },
105 + { "slug": "whisky", "name": "Whisky", "phase": 2, "index": "RARE-WINE", "conditionScale": "bottles" },
106 + { "slug": "rum", "name": "Rum", "phase": 3, "index": "RARE-WINE", "conditionScale": "bottles" },
107 + { "slug": "cognac", "name": "Cognac", "phase": 3, "index": "RARE-WINE", "conditionScale": "bottles" },
108 + { "slug": "perfume", "name": "Perfume", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "bottles" },
109 +
110 + { "slug": "luxury_handbags", "name": "Luxury Handbags", "phase": 2, "index": "RARE-LUXURY", "conditionScale": "luxury", "icon": "bag" },
111 + { "slug": "fashion_streetwear", "name": "Fashion & Streetwear", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "luxury" },
112 + { "slug": "jewelry", "name": "Jewelry", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "luxury" },
113 + { "slug": "gemstones", "name": "Gemstones", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "general", "compliance": ["certification_required"] },
114 + { "slug": "pens", "name": "Pens", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "general" },
115 + { "slug": "lighters", "name": "Lighters", "phase": 3, "index": "RARE-LUXURY", "conditionScale": "general" },
116 +
117 + { "slug": "minerals", "name": "Minerals", "phase": 3, "index": "RARE-FOSSIL", "conditionScale": "general" },
118 + { "slug": "meteorites", "name": "Meteorites", "phase": 3, "index": "RARE-FOSSIL", "conditionScale": "general" },
119 + { "slug": "fossils", "name": "Fossils", "phase": 3, "index": "RARE-FOSSIL", "conditionScale": "general", "compliance": ["jurisdiction_check", "provenance_required"] },
120 +
121 + { "slug": "antiques", "name": "Antiques", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
122 + { "slug": "porcelain", "name": "Porcelain", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
123 + { "slug": "glass_crystal", "name": "Glass & Crystal", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
124 + { "slug": "silver", "name": "Silver", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
125 + { "slug": "clocks", "name": "Clocks", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
126 + { "slug": "design_furniture", "name": "Design & Furniture", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
127 + { "slug": "scientific_instruments", "name": "Scientific Instruments", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
128 + { "slug": "typewriters", "name": "Typewriters", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
129 + { "slug": "vending_machines", "name": "Vending Machines", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "machines" },
130 +
131 + { "slug": "advertising", "name": "Advertising Collectibles", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
132 + { "slug": "disney_collectibles", "name": "Disney Collectibles", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
133 + { "slug": "sanrio", "name": "Sanrio", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
134 + { "slug": "star_wars", "name": "Star Wars (cross-category)", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
135 + { "slug": "marvel", "name": "Marvel (cross-category)", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
136 + { "slug": "dc", "name": "DC (cross-category)", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
137 + { "slug": "harry_potter", "name": "Harry Potter", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general" },
138 + { "slug": "lord_of_the_rings", "name": "Lord of the Rings", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general" },
139 + { "slug": "warhammer", "name": "Warhammer", "phase": 3, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
140 + { "slug": "gundam", "name": "Gundam & Gunpla", "phase": 3, "index": "RARE-TOY", "conditionScale": "boxed_toys" },
141 +
142 + { "slug": "travel_collectibles", "name": "Travel Collectibles", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
143 + { "slug": "airline_collectibles", "name": "Airline Collectibles", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
144 + { "slug": "aviation", "name": "Aviation", "phase": 3, "index": "RARE-SPACE", "conditionScale": "general" },
145 + { "slug": "space", "name": "Space Memorabilia", "phase": 3, "index": "RARE-SPACE", "conditionScale": "general" },
146 + { "slug": "militaria", "name": "Militaria", "phase": 3, "index": "RARE-MILITARIA", "conditionScale": "general", "compliance": ["jurisdiction_check", "restricted_symbols_review"] },
147 + { "slug": "medals", "name": "Medals", "phase": 3, "index": "RARE-MILITARIA", "conditionScale": "coins" },
148 + { "slug": "olympic_collectibles", "name": "Olympic Collectibles", "phase": 3, "index": "RARE-SPORT", "conditionScale": "general" },
149 + { "slug": "license_plates", "name": "License Plates", "phase": 3, "index": "RARE-AUTO", "conditionScale": "general" },
150 + { "slug": "pins", "name": "Pins", "phase": 3, "index": "RARE-TOY", "conditionScale": "general" },
151 + { "slug": "board_games", "name": "Board Games", "phase": 3, "index": "RARE-GAME", "conditionScale": "boxed_toys" },
152 + { "slug": "chess", "name": "Chess", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general" },
153 + { "slug": "playing_cards", "name": "Playing Cards", "phase": 3, "index": "RARE-TCG", "conditionScale": "general" },
154 + { "slug": "casino_memorabilia", "name": "Casino Memorabilia", "phase": 3, "index": "RARE-DESIGN", "conditionScale": "general", "compliance": ["jurisdiction_check"] },
155 + { "slug": "political_memorabilia", "name": "Political Memorabilia", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general", "compliance": ["neutral_presentation"] },
156 + { "slug": "royal_memorabilia", "name": "Royal Memorabilia", "phase": 3, "index": "RARE-BOOK", "conditionScale": "general" },
157 + { "slug": "corporate_memorabilia", "name": "Historic Corporate Memorabilia", "phase": 3, "index": "RARE-TECH", "conditionScale": "general" },
158 + { "slug": "digital_collectibles", "name": "Digital Collectibles", "phase": 3, "index": null, "conditionScale": "digital", "description": "NFTs, tokenized assets, game skins — separate valuation model (§100)", "compliance": ["separate_valuation_model"] }
159 + ]
160 +}
added data/taxonomy/conditions.json +69 −0
@@ -0,0 +1,69 @@
1 +{
2 + "$comment": "Category-specific condition scales (§117). `values` ordered best → worst. `aliases` map raw source strings (lowercased) to slugs.",
3 + "scales": {
4 + "cards": {
5 + "values": ["gem_mint", "mint", "near_mint", "excellent", "very_good", "good", "fair", "poor"],
6 + "completeness": [],
7 + "aliases": { "gem mint": "gem_mint", "gem mt": "gem_mint", "mint": "mint", "nm": "near_mint", "near mint": "near_mint", "nm-mt": "near_mint", "nm/m": "near_mint", "lp": "excellent", "lightly played": "excellent", "ex": "excellent", "excellent": "excellent", "mp": "very_good", "moderately played": "very_good", "vg": "very_good", "hp": "good", "heavily played": "good", "good": "good", "damaged": "poor", "poor": "poor", "dmg": "poor", "raw": null, "ungraded": null }
8 + },
9 + "video_games": {
10 + "values": ["sealed", "cib", "loose"],
11 + "completeness": ["sealed", "cib", "box_only", "manual_only", "loose"],
12 + "aliases": { "new": "sealed", "sealed": "sealed", "factory sealed": "sealed", "brand new": "sealed", "cib": "cib", "complete in box": "cib", "complete": "cib", "boxed": "cib", "loose": "loose", "cart only": "loose", "cartridge only": "loose", "disc only": "loose", "used": "loose", "graded": null }
13 + },
14 + "sneakers": {
15 + "values": ["new", "deadstock", "lightly_used", "used"],
16 + "completeness": ["with_box", "no_box", "replacement_box"],
17 + "aliases": { "new": "new", "ds": "deadstock", "deadstock": "deadstock", "brand new": "new", "vnds": "lightly_used", "lightly used": "lightly_used", "pre-owned": "used", "used": "used", "worn": "used" }
18 + },
19 + "watches": {
20 + "values": ["unworn", "excellent", "good", "fair", "poor", "parts"],
21 + "completeness": ["full_set", "box_papers", "papers_only", "box_only", "watch_only"],
22 + "aliases": { "new": "unworn", "unworn": "unworn", "new with tags": "unworn", "like new": "excellent", "excellent": "excellent", "very good": "excellent", "good": "good", "fair": "fair", "poor": "poor", "for parts": "parts", "parts": "parts", "incomplete": "parts", "pre-owned": null, "used": null }
23 + },
24 + "books": {
25 + "values": ["fine", "very_good", "good", "fair", "poor"],
26 + "completeness": ["dust_jacket", "no_dust_jacket", "slipcase"],
27 + "aliases": { "as new": "fine", "fine": "fine", "f": "fine", "near fine": "fine", "vg": "very_good", "very good": "very_good", "good": "good", "g": "good", "fair": "fair", "poor": "poor", "reading copy": "poor" }
28 + },
29 + "comics": {
30 + "values": ["gem_mint", "mint", "near_mint", "very_fine", "fine", "very_good", "good", "fair", "poor"],
31 + "completeness": [],
32 + "aliases": { "nm": "near_mint", "near mint": "near_mint", "vf": "very_fine", "very fine": "very_fine", "fn": "fine", "fine": "fine", "vg": "very_good", "very good": "very_good", "gd": "good", "good": "good", "fr": "fair", "fair": "fair", "pr": "poor", "poor": "poor" }
33 + },
34 + "lego": {
35 + "values": ["sealed", "opened_complete", "used_complete", "incomplete"],
36 + "completeness": ["sealed", "opened_complete", "used_complete", "incomplete", "minifigure_only", "instructions_only"],
37 + "aliases": { "new": "sealed", "sealed": "sealed", "misb": "sealed", "nisb": "sealed", "new in sealed box": "sealed", "complete": "opened_complete", "used complete": "used_complete", "used": "used_complete", "incomplete": "incomplete" }
38 + },
39 + "boxed_toys": {
40 + "values": ["mint_in_box", "near_mint_box", "boxed", "loose_complete", "loose"],
41 + "completeness": ["sealed", "boxed", "loose"],
42 + "aliases": { "mib": "mint_in_box", "misb": "mint_in_box", "moc": "mint_in_box", "nib": "mint_in_box", "new": "mint_in_box", "boxed": "boxed", "in box": "boxed", "loose": "loose", "complete": "loose_complete" }
43 + },
44 + "coins": {
45 + "values": ["proof", "mint_state", "about_uncirculated", "extremely_fine", "very_fine", "fine", "very_good", "good", "about_good", "poor"],
46 + "completeness": [],
47 + "aliases": { "pf": "proof", "pr": "proof", "proof": "proof", "ms": "mint_state", "unc": "mint_state", "bu": "mint_state", "au": "about_uncirculated", "xf": "extremely_fine", "ef": "extremely_fine", "vf": "very_fine", "f": "fine", "vg": "very_good", "g": "good", "ag": "about_good", "p": "poor" }
48 + },
49 + "records": {
50 + "values": ["mint", "near_mint", "very_good_plus", "very_good", "good_plus", "good", "fair", "poor"],
51 + "completeness": ["sealed", "with_sleeve", "no_sleeve"],
52 + "aliases": { "m": "mint", "sealed": "mint", "nm": "near_mint", "vg+": "very_good_plus", "vg": "very_good", "g+": "good_plus", "g": "good", "f": "fair", "p": "poor" }
53 + },
54 + "luxury": {
55 + "values": ["new", "pristine", "excellent", "very_good", "good", "fair"],
56 + "completeness": ["full_set", "dust_bag", "box", "receipt", "item_only"],
57 + "aliases": { "new": "new", "new with tags": "new", "pristine": "pristine", "excellent": "excellent", "very good": "very_good", "good": "good", "fair": "fair" }
58 + },
59 + "art": { "values": ["excellent", "very_good", "good", "fair", "restored"], "completeness": ["framed", "unframed", "coa"], "aliases": {} },
60 + "paper": { "values": ["mint", "near_mint", "very_fine", "fine", "very_good", "good", "fair", "poor"], "completeness": ["rolled", "folded", "linen_backed"], "aliases": {} },
61 + "electronics": { "values": ["sealed", "mint", "excellent", "good", "fair", "for_parts"], "completeness": ["sealed", "boxed", "unit_only"], "aliases": { "sealed": "sealed", "new": "sealed", "mint": "mint", "excellent": "excellent", "good": "good", "fair": "fair", "for parts": "for_parts", "not working": "for_parts" } },
62 + "bottles": { "values": ["pristine", "excellent", "good", "fair"], "completeness": ["owc", "oc", "sealed", "opened"], "aliases": {} },
63 + "vehicles": { "values": ["concours", "excellent", "good", "fair", "project"], "completeness": [], "aliases": {} },
64 + "machines": { "values": ["restored", "working_original", "working", "non_working", "project"], "completeness": [], "aliases": {} },
65 + "stamps": { "values": ["superb", "extremely_fine", "very_fine", "fine", "average", "poor"], "completeness": ["mnh", "mh", "used"], "aliases": {} },
66 + "general": { "values": ["excellent", "very_good", "good", "fair", "poor"], "completeness": [], "aliases": { "excellent": "excellent", "very good": "very_good", "good": "good", "fair": "fair", "poor": "poor" } },
67 + "digital": { "values": [], "completeness": [], "aliases": {} }
68 + }
69 +}
added data/taxonomy/graders.json +24 −0
@@ -0,0 +1,24 @@
1 +{
2 + "$comment": "Grading companies (§118). Grades are tracked per grader; premiums are measured empirically, never assumed equal across graders.",
3 + "graders": [
4 + { "slug": "psa", "name": "PSA", "categories": ["trading_cards", "sports_cards", "non_sport_cards"], "scale": { "type": "numeric", "values": ["1", "1.5", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "top": "10" }, "populationUrl": "https://www.psacard.com/pop", "verifyUrl": "https://www.psacard.com/cert/{cert}", "aliases": ["psa", "professional sports authenticator"] },
5 + { "slug": "bgs", "name": "BGS (Beckett)", "categories": ["trading_cards", "sports_cards"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "7.5", "8", "8.5", "9", "9.5", "10"], "top": "10" }, "populationUrl": "https://www.beckett.com/grading/pop-report", "verifyUrl": "https://www.beckett.com/grading/card-lookup", "aliases": ["bgs", "beckett", "bccg"] },
6 + { "slug": "cgc", "name": "CGC", "categories": ["trading_cards", "sports_cards", "comics", "manga", "video_games"], "scale": { "type": "numeric", "values": ["0.5", "1", "1.5", "2", "2.5", "3", "3.5", "4", "4.5", "5", "5.5", "6", "6.5", "7", "7.5", "8", "8.5", "9", "9.2", "9.4", "9.6", "9.8", "9.9", "10"], "top": "10" }, "populationUrl": "https://www.cgccomics.com/census/", "verifyUrl": "https://www.cgccomics.com/certlookup/{cert}", "aliases": ["cgc"] },
7 + { "slug": "sgc", "name": "SGC", "categories": ["trading_cards", "sports_cards"], "scale": { "type": "numeric", "values": ["1", "1.5", "2", "2.5", "3", "3.5", "4", "4.5", "5", "5.5", "6", "6.5", "7", "7.5", "8", "8.5", "9", "9.5", "10"], "top": "10" }, "populationUrl": "https://gosgc.com/pop-report", "verifyUrl": "https://gosgc.com/card-registry", "aliases": ["sgc"] },
8 + { "slug": "tag", "name": "TAG", "categories": ["trading_cards", "sports_cards"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "8", "8.5", "9", "9.5", "10"], "top": "10" }, "aliases": ["tag grading"] },
9 + { "slug": "ace", "name": "ACE Grading", "categories": ["trading_cards"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "top": "10" }, "aliases": ["ace"] },
10 + { "slug": "cbcs", "name": "CBCS", "categories": ["comics"], "scale": { "type": "numeric", "values": ["0.5", "1", "2", "3", "4", "5", "6", "7", "8", "9", "9.2", "9.4", "9.6", "9.8", "10"], "top": "10" }, "aliases": ["cbcs"] },
11 + { "slug": "pgx", "name": "PGX", "categories": ["comics"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "9.8", "10"], "top": "10" }, "aliases": ["pgx"] },
12 + { "slug": "wata", "name": "WATA", "categories": ["video_games"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "7.5", "8", "8.5", "9", "9.2", "9.4", "9.6", "9.8", "10"], "top": "10" }, "populationUrl": "https://www.watagames.com/population", "aliases": ["wata"] },
13 + { "slug": "vga", "name": "VGA", "categories": ["video_games", "vintage_toys"], "scale": { "type": "numeric", "values": ["10", "20", "30", "40", "50", "60", "65", "70", "75", "80", "85", "90", "95", "100"], "top": "100" }, "aliases": ["vga"] },
14 + { "slug": "pcgs", "name": "PCGS", "categories": ["coins", "banknotes"], "scale": { "type": "sheldon", "values": ["1", "2", "3", "4", "6", "8", "10", "12", "15", "20", "25", "30", "35", "40", "45", "50", "53", "55", "58", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70"], "top": "70" }, "populationUrl": "https://www.pcgs.com/pop", "verifyUrl": "https://www.pcgs.com/cert/{cert}", "aliases": ["pcgs"] },
15 + { "slug": "ngc", "name": "NGC", "categories": ["coins", "medals"], "scale": { "type": "sheldon", "values": ["1", "2", "3", "4", "6", "8", "10", "12", "15", "20", "25", "30", "35", "40", "45", "50", "53", "55", "58", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70"], "top": "70" }, "populationUrl": "https://www.ngccoin.com/census/", "verifyUrl": "https://www.ngccoin.com/certlookup/{cert}", "aliases": ["ngc"] },
16 + { "slug": "anacs", "name": "ANACS", "categories": ["coins"], "scale": { "type": "sheldon", "values": [], "top": "70" }, "aliases": ["anacs"] },
17 + { "slug": "iccs", "name": "ICCS", "categories": ["coins"], "scale": { "type": "sheldon", "values": [], "top": "70" }, "aliases": ["iccs"] },
18 + { "slug": "pmg", "name": "PMG", "categories": ["banknotes"], "scale": { "type": "sheldon", "values": [], "top": "70" }, "aliases": ["pmg"] },
19 + { "slug": "psa_dna", "name": "PSA/DNA", "categories": ["autographs", "sports_memorabilia"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "top": "10" }, "aliases": ["psa/dna", "psa dna"] },
20 + { "slug": "jsa", "name": "JSA", "categories": ["autographs", "sports_memorabilia"], "scale": { "type": "descriptive", "values": ["authentic"], "top": "authentic" }, "aliases": ["jsa"] },
21 + { "slug": "beckett_auth", "name": "Beckett Authentication", "categories": ["autographs"], "scale": { "type": "numeric", "values": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "top": "10" }, "aliases": ["bas", "beckett authentication"] },
22 + { "slug": "raw", "name": "Raw / Ungraded", "categories": [], "scale": { "type": "descriptive", "values": [], "top": "" }, "aliases": ["raw", "ungraded"] }
23 + ]
24 +}
added data/taxonomy/indices.json +28 −0
@@ -0,0 +1,28 @@
1 +{
2 + "$comment": "Index definitions (§3). Family slugs map categories → subindex; RARE aggregates all subindices. base_value 1000 at base_date. Values are only computed from observed data; an index publishes nothing until it has ≥ minConstituents priced constituents.",
3 + "baseDate": "2024-01-01",
4 + "baseValue": 1000,
5 + "indices": [
6 + { "ticker": "RARE", "name": "RareIndex Global Collectibles Index", "flagship": true, "description": "Broad benchmark of the global collectibles market, aggregating all RareIndex subindices.", "families": [], "color": "#0f172a", "minConstituents": 25 },
7 + { "ticker": "RARE-TCG", "name": "RareIndex Trading Cards", "families": ["trading_cards", "playing_cards"], "color": "#b45309", "minConstituents": 10 },
8 + { "ticker": "RARE-SPORT", "name": "RareIndex Sports", "families": ["sports_cards", "sports_memorabilia", "olympic_collectibles"], "color": "#1d4ed8", "minConstituents": 10 },
9 + { "ticker": "RARE-WATCH", "name": "RareIndex Watches", "families": ["watches"], "color": "#334155", "minConstituents": 10 },
10 + { "ticker": "RARE-SNEAKER", "name": "RareIndex Sneakers", "families": ["sneakers"], "color": "#dc2626", "minConstituents": 10 },
11 + { "ticker": "RARE-LEGO", "name": "RareIndex LEGO", "families": ["lego"], "color": "#ca8a04", "minConstituents": 10 },
12 + { "ticker": "RARE-COMIC", "name": "RareIndex Comics", "families": ["comics"], "color": "#7c3aed", "minConstituents": 10 },
13 + { "ticker": "RARE-GAME", "name": "RareIndex Video Games", "families": ["video_games", "gaming_hardware", "arcade_pinball", "board_games"], "color": "#059669", "minConstituents": 10 },
14 + { "ticker": "RARE-ART", "name": "RareIndex Art", "families": ["art", "movie_memorabilia", "movie_posters", "animation_art", "photography"], "color": "#be185d", "minConstituents": 10 },
15 + { "ticker": "RARE-LUXURY", "name": "RareIndex Luxury", "families": ["luxury_handbags", "fashion_streetwear", "jewelry", "gemstones", "pens", "lighters", "perfume"], "color": "#a16207", "minConstituents": 10 },
16 + { "ticker": "RARE-WINE", "name": "RareIndex Wine & Spirits", "families": ["wine", "whisky", "rum", "cognac"], "color": "#7f1d1d", "minConstituents": 10 },
17 + { "ticker": "RARE-COIN", "name": "RareIndex Coins & Currency", "families": ["coins", "banknotes", "stamps"], "color": "#92400e", "minConstituents": 10 },
18 + { "ticker": "RARE-TOY", "name": "RareIndex Toys", "families": ["funko", "designer_toys", "action_figures", "vintage_toys", "dolls", "plush", "model_cars", "model_trains", "disney_collectibles", "sanrio", "star_wars", "marvel", "dc", "warhammer", "gundam", "pins"], "color": "#ea580c", "minConstituents": 10 },
19 + { "ticker": "RARE-MUSIC", "name": "RareIndex Music", "families": ["music", "music_memorabilia", "musical_instruments"], "color": "#4338ca", "minConstituents": 10 },
20 + { "ticker": "RARE-AUTO", "name": "RareIndex Automotive", "families": ["automobiles", "motorcycles", "automotive_memorabilia", "formula_1", "license_plates"], "color": "#0f766e", "minConstituents": 10 },
21 + { "ticker": "RARE-TECH", "name": "RareIndex Technology", "families": ["apple_collectibles", "vintage_computers", "computer_hardware", "mobile_phones", "keyboards", "audio_equipment", "cameras", "corporate_memorabilia"], "color": "#475569", "minConstituents": 10 },
22 + { "ticker": "RARE-BOOK", "name": "RareIndex Books & Paper", "families": ["books", "manga", "historical_documents", "autographs", "maps", "postcards", "harry_potter", "lord_of_the_rings", "political_memorabilia", "royal_memorabilia"], "color": "#713f12", "minConstituents": 10 },
23 + { "ticker": "RARE-MILITARIA", "name": "RareIndex Militaria & Medals", "families": ["militaria", "medals"], "color": "#3f3f46", "minConstituents": 10 },
24 + { "ticker": "RARE-SPACE", "name": "RareIndex Space & Aviation", "families": ["space", "aviation"], "color": "#1e3a8a", "minConstituents": 10 },
25 + { "ticker": "RARE-FOSSIL", "name": "RareIndex Natural History", "families": ["fossils", "minerals", "meteorites"], "color": "#65a30d", "minConstituents": 10 },
26 + { "ticker": "RARE-DESIGN", "name": "RareIndex Design & Antiques", "families": ["antiques", "porcelain", "glass_crystal", "silver", "clocks", "design_furniture", "scientific_instruments", "typewriters", "vending_machines", "advertising", "travel_collectibles", "airline_collectibles", "chess", "casino_memorabilia"], "color": "#57534e", "minConstituents": 10 }
27 + ]
28 +}
added docs/ARCHITECTURE.md +76 −0
@@ -0,0 +1,76 @@
1 +# RareIndex — Architecture (Phase 1 foundation)
2 +
3 +This document is the practical companion to `CLAUDE.md` (the product & engineering mandate). It
4 +describes what exists in the repository, how the pieces fit, and the decisions taken so far.
5 +
6 +## Monorepo
7 +
8 +pnpm workspaces, TypeScript strict everywhere, ESM.
9 +
10 +```
11 +apps/web Next.js 16 (App Router, React 19, Tailwind v4) — public site, account, admin
12 +apps/api Fastify — public REST API /v1 (API keys, tiers, rate limits, exports)
13 +packages/shared ids, money/FX helpers, canonical Zod schemas, stats helpers, logger, env
14 +packages/database Drizzle ORM schema (55 tables), migrations, client, seed
15 +packages/taxonomy extensible category tree, condition scales, graders, index definitions (data/taxonomy)
16 +packages/connectors connector framework: interface, Firecrawl/Scrapfly/HTTP engines, router, quality, health, fixtures
17 +packages/valuation price engine (RIV), outliers, grade premiums, scores
18 +packages/indices index computation (chain-linked, repeat-sales), category snapshots, correlations
19 +packages/search hybrid search (FTS + trigram + optional vectors) and natural-language query parsing
20 +packages/ai ModelProvider abstraction (Anthropic, OpenAI-compatible), extraction, embeddings, vision
21 +connectors/ source connectors grouped by primary engine (api/, feeds/, firecrawl/, scrapfly/) + registry.json
22 +workers/ pipeline processes: crawler, normalizer, entity-resolution, valuation, image-processing, indices
23 +data/taxonomy seed JSON for the taxonomy
24 +data/fixtures captured real payloads per connector for tests
25 +scripts/ operational CLI (`ri`), registry builder, deploy helpers
26 +docs/ this file, ADRs, methodology
27 +```
28 +
29 +## Data flow (never write scraped data into product tables directly — §108)
30 +
31 +```
32 +connector.crawl() → raw_records (immutable, content-hash dedupe, snapshot on disk)
33 + → connector.normalize(raw) → normalized_records (staging, auditable)
34 + → entity resolution (identifiers → canonical key → fuzzy → embeddings/LLM)
35 + → assets / asset_variants (+ sets, brands)
36 + → dedupe + price validation (flags, audit_log; nothing deleted)
37 + → sales / listings / auction_lots / price_observations (native currency + USD at historical FX)
38 + → valuations, asset_stats, price_snapshots
39 + → indices (index_values), category_snapshots, radar_findings
40 +```
41 +
42 +Queue: pg-boss (PostgreSQL-backed) behind a tiny abstraction so Redis Streams/Kafka can replace it
43 +at scale (§140). Domain events are also appended to `events`.
44 +
45 +## Engines & routing (§198)
46 +
47 +`packages/connectors/src/router.ts`: `api/feed (direct HTTP) → firecrawl → scrapfly → requiresReview`.
48 +Each engine result is scored (§199) by the connector's parser through `FetchOptions.parse/expect`.
49 +Engines are only used on publicly accessible pages, with an honest user agent and rate limiting (§179).
50 +
51 +## Connector contract (§102, §197)
52 +
53 +`connectors/<engine>/<id>/{meta.json,index.ts,index.test.ts}` + `data/fixtures/<id>/*.json`.
54 +`scripts/build-registry.ts` assembles `connectors/registry.json` from the `meta.json` files.
55 +`packages/database/src/seed.ts` mirrors the registry into `sources`/`connectors` tables.
56 +
57 +## Canonical model highlights
58 +
59 +- `assets` = the object independent of grade/condition; `asset_variants` = grade/condition/size slice
60 + (PSA 10, CIB, deadstock size 10). Sales, listings and valuations reference both.
61 +- `canonical_key` (deterministic normalised key) + `identifiers` (JSONB of external ids) power entity
62 + resolution; merges are recorded in `merged_from` and `audit_log`.
63 +- Prices: `NUMERIC(18,4)` native + `price_usd` + `fx_rate`/`fx_date`. FX from ECB (frankfurter) stored
64 + in `fx_rates`; historical conversions always use the rate of the sale date (§136).
65 +- Price-guide numbers (Scryfall, TCG APIs) are `price_observations`, never `sales`.
66 +- Every statistic exposes sample size, confidence and last update (§191); unknown stays NULL (§192).
67 +
68 +## Deployment
69 +
70 +Target: MacLustr node with PostgreSQL 17 + pgvector, deployed through the `mld` gateway (M1M32).
71 +Processes (PM2): `rareindex-web` (Next.js), `rareindex-api` (Fastify), `rareindex-worker` (pipeline),
72 +`rareindex-ngrok` (www.rareindex.io). Secrets live only in the mld manifest / `.env` on the node.
73 +
74 +## Decisions log
75 +
76 +See `docs/adr/`.
added docs/adr/0001-stack-and-layout.md +28 −0
@@ -0,0 +1,28 @@
1 +# ADR 0001 — TypeScript end-to-end, pnpm monorepo, PostgreSQL-backed queue
2 +
3 +Date: 2026-09-07 · Status: accepted
4 +
5 +## Context
6 +CLAUDE.md allows Node/TypeScript or Python services (§167) and suggests a layout (§195). The launch
7 +wedge must ship quickly on a single node while the architecture must scale to hundreds of millions
8 +of observations.
9 +
10 +## Decision
11 +1. **TypeScript everywhere** (web, API, workers, connectors). One runtime, shared Zod schemas between
12 + pipeline and UI, Drizzle typed queries, and the connector interface exactly as specified (§102).
13 +2. **pnpm workspaces**, packages consumed as TS sources (`transpilePackages` in Next, `tsx` for workers).
14 +3. **PostgreSQL 17** as system of record (pg_trgm, unaccent, pgvector). No Timescale for now; time
15 + series tables (`price_snapshots`, `index_values`) use composite PKs and date indexes; partitioning
16 + can be added by migration.
17 +4. **pg-boss** as the queue (Postgres-backed), wrapped by a small `Queue` interface in `workers/lib`
18 + so Redis Streams/Kafka can be substituted without touching connectors or handlers (§140).
19 +5. **Admin UI lives inside `apps/web` under `/admin`** (token-protected) instead of a separate
20 + `apps/admin`, to avoid duplicating the design system in Phase 1. It can be split later.
21 +6. **Registry from meta files**: `connectors/registry.json` is generated from per-connector
22 + `meta.json` (avoids merge conflicts between parallel contributors) — it remains the artefact §106 asks for.
23 +
24 +## Consequences
25 +- Data-science-heavy methods (hedonic regression, repeat-sales) are implemented in TS with small
26 + numerical helpers; a Python research service can be added later behind the same tables.
27 +- Everything deploys as PM2 processes on one node via `mld`; horizontal scale = more worker processes
28 + pointed at the same Postgres/pg-boss.
added docs/adr/0002-data-honesty.md +15 −0
@@ -0,0 +1,15 @@
1 +# ADR 0002 — Data honesty rules enforced in code
2 +
3 +Date: 2026-09-07 · Status: accepted
4 +
5 +- No fabricated data, ever (§192). Seeds contain taxonomy and index *definitions* only. Sales, listings,
6 + prices, populations and market caps come from connectors with provenance (`source_url`, `raw_record_id`,
7 + `parser_version`, `connector_version`).
8 +- Price-guide values (Scryfall/TCG APIs, guide pages) are stored as `price_observations`, shown as
9 + "guide" numbers and weighted below transactions in RIV. They never populate `sales`.
10 +- A valuation is published only with `sample_size`, `confidence`, `low/high` and `computed_at`. Below
11 + the evidence threshold the UI renders "Data unavailable" (`<Unavailable/>`).
12 +- Indices publish nothing until `minConstituents` priced constituents exist; coverage is stored per day.
13 +- Outliers are flagged (`status='flagged'|'excluded'`, `flags[]`, `audit_log`) — never deleted.
14 +- Crawl time ≠ sale time: connectors must parse the source's own date; the fixture suite checks it.
15 +- Historical FX is applied at the sale date; the display currency is a view-time conversion.
added package.json +31 −0
@@ -0,0 +1,31 @@
1 +{
2 + "name": "rareindex",
3 + "version": "0.1.0",
4 + "private": true,
5 + "description": "RareIndex.io — global collectibles market intelligence, search, pricing & index platform",
6 + "type": "module",
7 + "packageManager": "pnpm@11.1.2",
8 + "engines": {
9 + "node": ">=22"
10 + },
11 + "scripts": {
12 + "build": "pnpm -r --filter './packages/**' run build && pnpm --filter @rareindex/api run build && pnpm --filter @rareindex/web run build",
13 + "build:packages": "pnpm -r --filter './packages/**' run build",
14 + "typecheck": "pnpm -r run typecheck",
15 + "test": "pnpm -r run test",
16 + "lint": "pnpm -r run lint",
17 + "db:generate": "pnpm --filter @rareindex/database run generate",
18 + "db:migrate": "pnpm --filter @rareindex/database run migrate",
19 + "db:seed": "pnpm --filter @rareindex/database run seed",
20 + "dev:web": "pnpm --filter @rareindex/web run dev",
21 + "dev:api": "pnpm --filter @rareindex/api run dev",
22 + "worker": "tsx workers/main.ts",
23 + "ri": "tsx scripts/ri.ts"
24 + },
25 + "devDependencies": {
26 + "@types/node": "^24.0.0",
27 + "tsx": "^4.20.0",
28 + "typescript": "^5.9.3",
29 + "vitest": "^3.2.0"
30 + }
31 +}
added packages/connectors/package.json +32 −0
@@ -0,0 +1,32 @@
1 +{
2 + "name": "@rareindex/connectors",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + },
11 + "./testing": {
12 + "types": "./src/testing.ts",
13 + "default": "./src/testing.ts"
14 + }
15 + },
16 + "scripts": {
17 + "build": "tsc -p tsconfig.json --noEmit",
18 + "typecheck": "tsc -p tsconfig.json --noEmit",
19 + "test": "vitest run --passWithNoTests"
20 + },
21 + "dependencies": {
22 + "@rareindex/shared": "workspace:*",
23 + "@rareindex/taxonomy": "workspace:*",
24 + "cheerio": "^1.1.0",
25 + "zod": "^4.0.0"
26 + },
27 + "devDependencies": {
28 + "@types/node": "^24.0.0",
29 + "typescript": "^5.9.3",
30 + "vitest": "^3.2.0"
31 + }
32 +}
added packages/connectors/src/base.ts +49 −0
@@ -0,0 +1,49 @@
1 +import { sha256, type ConnectorHealth, type NormalizedRecord } from '@rareindex/shared';
2 +import { healthFromRuns } from './health.js';
3 +import type { ConnectorMeta, CrawlContext, HealthContext, RareIndexConnector, RawRecordInput, RawRecordLike } from './types.js';
4 +
5 +/**
6 + * Convenience base class. Connectors may implement the interface directly; this class adds
7 + * rate limiting, checkpointing helpers and a default health check.
8 + */
9 +export abstract class BaseConnector implements RareIndexConnector {
10 + abstract readonly version: string;
11 + abstract readonly parserVersion: string;
12 + readonly urlPatterns?: RegExp[];
13 + /** minimum ms between requests to the source (politeness) */
14 + protected minIntervalMs = 500;
15 + private lastRequestAt = 0;
16 +
17 + constructor(public readonly meta: ConnectorMeta) {}
18 +
19 + abstract crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput>;
20 + abstract normalize(raw: RawRecordLike): Promise<NormalizedRecord[]>;
21 +
22 + async healthCheck(ctx: HealthContext): Promise<ConnectorHealth> {
23 + const h = healthFromRuns(ctx, this.meta.schemaVersion);
24 + if (ctx.recentRuns.length === 0) {
25 + // Fresh connector: light probe of the source root.
26 + const res = await ctx.fetch(this.meta.sourceUrl, { engines: ['api'], failOnHttpError: false, responseType: 'text', minQuality: 0 });
27 + h.status = res.httpStatus && res.httpStatus < 500 ? 'unknown' : 'failing';
28 + h.last_error = res.error;
29 + }
30 + return h;
31 + }
32 +
33 + /** Politeness delay between requests. */
34 + protected async throttle(): Promise<void> {
35 + const wait = this.lastRequestAt + this.minIntervalMs - Date.now();
36 + if (wait > 0) await new Promise((r) => setTimeout(r, wait));
37 + this.lastRequestAt = Date.now();
38 + }
39 +
40 + /** Stable content hash for dedupe of raw payloads. */
41 + protected hash(payload: unknown): string {
42 + return sha256(typeof payload === 'string' ? payload : JSON.stringify(payload));
43 + }
44 +
45 + /** Helper: yield-limit aware counter. */
46 + protected reached(ctx: CrawlContext, count: number): boolean {
47 + return ctx.options.limit !== undefined && count >= ctx.options.limit;
48 + }
49 +}
added packages/connectors/src/context.ts +94 −0
@@ -0,0 +1,94 @@
1 +import { childLogger, sha256, type Logger } from '@rareindex/shared';
2 +import type { Router } from './router.js';
3 +import type { ConnectorMeta, CrawlContext, CrawlOptions, EngineStats, FetchOptions, HealthContext } from './types.js';
4 +
5 +export interface BudgetStore {
6 + /** return previously stored content hash + next due time for url */
7 + get(urlHash: string): Promise<{ contentHash: string | null; nextFetchAt: Date | null } | null>;
8 + /** record a fetch outcome; implementation updates change-interval estimate (§170) */
9 + record(input: { urlHash: string; url: string; connectorId: string; contentHash: string | null; changed: boolean; status: number | null }): Promise<void>;
10 +}
11 +
12 +export interface ContextDeps {
13 + router: Router;
14 + meta: ConnectorMeta;
15 + options: CrawlOptions;
16 + log?: Logger;
17 + budget?: BudgetStore;
18 + onCursor?: (cursor: Record<string, unknown>) => Promise<void>;
19 + onAnomaly?: (kind: string, detail?: string) => void;
20 + signal?: AbortSignal;
21 +}
22 +
23 +/** Build the CrawlContext handed to connectors; wires the router, budget store and stats. */
24 +export function createCrawlContext(deps: ContextDeps): CrawlContext & { anomalies: string[] } {
25 + const engineStats: Record<string, EngineStats> = {};
26 + const anomalies: string[] = [];
27 + const log = deps.log ?? childLogger({ connector: deps.meta.id });
28 + const fc = deps.router.engines.firecrawl;
29 +
30 + const ctx: CrawlContext & { anomalies: string[] } = {
31 + meta: deps.meta,
32 + log,
33 + options: deps.options,
34 + engineStats,
35 + anomalies,
36 + signal: deps.signal,
37 + async fetch(url: string, opts: FetchOptions = {}) {
38 + const res = await deps.router.fetch(url, opts, { enginePriority: deps.meta.enginePriority, stats: engineStats });
39 + if (deps.budget && !opts.force) {
40 + const urlHash = sha256(url);
41 + const content = res.json !== null && res.json !== undefined ? JSON.stringify(res.json) : (res.html ?? res.markdown ?? '');
42 + const contentHash = content ? sha256(content) : null;
43 + const prev = await deps.budget.get(urlHash);
44 + await deps.budget.record({ urlHash, url, connectorId: deps.meta.id, contentHash, changed: prev?.contentHash !== contentHash, status: res.httpStatus });
45 + }
46 + return res;
47 + },
48 + search: fc
49 + ? async (query, o = {}) => {
50 + const r = await fc.search(query, o);
51 + const s = (engineStats.firecrawl ??= { attempts: 0, success: 0, credits: 0, ms: 0 });
52 + s.attempts++;
53 + s.success++;
54 + s.credits += r.credits;
55 + return r.items;
56 + }
57 + : undefined,
58 + map: fc
59 + ? async (url, o = {}) => {
60 + const r = await fc.map(url, o);
61 + const s = (engineStats.firecrawl ??= { attempts: 0, success: 0, credits: 0, ms: 0 });
62 + s.attempts++;
63 + s.success++;
64 + s.credits += r.credits;
65 + return r.links;
66 + }
67 + : undefined,
68 + async shouldFetch(url: string) {
69 + if (!deps.budget || deps.options.mode === 'backfill') return true;
70 + const prev = await deps.budget.get(sha256(url));
71 + if (!prev || !prev.nextFetchAt) return true;
72 + return prev.nextFetchAt.getTime() <= Date.now();
73 + },
74 + async setCursor(cursor) {
75 + await deps.onCursor?.(cursor);
76 + },
77 + anomaly(kind, detail) {
78 + const s = detail ? `${kind}: ${detail}` : kind;
79 + anomalies.push(s);
80 + deps.onAnomaly?.(kind, detail);
81 + log.warn({ kind, detail }, 'connector anomaly');
82 + },
83 + };
84 + return ctx;
85 +}
86 +
87 +export function createHealthContext(deps: { router: Router; meta: ConnectorMeta; recentRuns: HealthContext['recentRuns']; log?: Logger }): HealthContext {
88 + return {
89 + meta: deps.meta,
90 + log: deps.log ?? childLogger({ connector: deps.meta.id }),
91 + recentRuns: deps.recentRuns,
92 + fetch: (url, opts) => deps.router.fetch(url, opts, { enginePriority: deps.meta.enginePriority }),
93 + };
94 +}
added packages/connectors/src/engines/firecrawl.ts +112 −0
@@ -0,0 +1,112 @@
1 +import type { ExtractionResult } from '@rareindex/shared';
2 +import type { FetchOptions } from '../types.js';
3 +
4 +export interface FirecrawlEngineOptions {
5 + apiKey: string;
6 + baseUrl?: string;
7 + defaultTimeoutMs?: number;
8 +}
9 +
10 +interface FirecrawlScrapeResponse {
11 + success: boolean;
12 + data?: {
13 + markdown?: string;
14 + html?: string;
15 + rawHtml?: string;
16 + json?: unknown;
17 + links?: string[];
18 + metadata?: { statusCode?: number; sourceURL?: string; url?: string; title?: string; error?: string; creditsUsed?: number };
19 + };
20 + error?: string;
21 + creditsUsed?: number;
22 +}
23 +
24 +/**
25 + * Firecrawl v2 REST client (§103). Preferred engine for parseable public pages: returns markdown +
26 + * HTML (+ optional JSON extraction with a schema). Never used to bypass access controls (§179).
27 + */
28 +export function createFirecrawlEngine(opts: FirecrawlEngineOptions) {
29 + const base = (opts.baseUrl ?? 'https://api.firecrawl.dev').replace(/\/$/, '');
30 + const headers = { authorization: `Bearer ${opts.apiKey}`, 'content-type': 'application/json' };
31 +
32 + async function post<T>(path: string, body: unknown, timeoutMs: number): Promise<T> {
33 + const ctrl = new AbortController();
34 + const timer = setTimeout(() => ctrl.abort(), timeoutMs + 5_000);
35 + try {
36 + const res = await fetch(`${base}${path}`, { method: 'POST', headers, body: JSON.stringify(body), signal: ctrl.signal });
37 + const text = await res.text();
38 + let json: unknown = null;
39 + try {
40 + json = JSON.parse(text);
41 + } catch {
42 + throw new Error(`firecrawl ${path} HTTP ${res.status}: ${text.slice(0, 200)}`);
43 + }
44 + if (!res.ok) {
45 + const msg = (json as { error?: string })?.error ?? text.slice(0, 200);
46 + throw new Error(`firecrawl ${path} HTTP ${res.status}: ${msg}`);
47 + }
48 + return json as T;
49 + } finally {
50 + clearTimeout(timer);
51 + }
52 + }
53 +
54 + async function scrape(url: string, o: FetchOptions = {}): Promise<ExtractionResult> {
55 + const started = Date.now();
56 + const timeoutMs = o.timeoutMs ?? opts.defaultTimeoutMs ?? 60_000;
57 + const formats: unknown[] = ['markdown', 'html'];
58 + if (o.jsonSchema || o.jsonPrompt) formats.push({ type: 'json', ...(o.jsonSchema ? { schema: o.jsonSchema } : {}), ...(o.jsonPrompt ? { prompt: o.jsonPrompt } : {}) });
59 + const body: Record<string, unknown> = {
60 + url,
61 + formats,
62 + onlyMainContent: false,
63 + timeout: timeoutMs,
64 + ...(o.waitForMs ? { waitFor: o.waitForMs } : {}),
65 + ...(o.country ? { location: { country: o.country.toUpperCase() } } : {}),
66 + ...(o.headers ? { headers: o.headers } : {}),
67 + };
68 + try {
69 + const r = await post<FirecrawlScrapeResponse>('/v2/scrape', body, timeoutMs);
70 + const d = r.data;
71 + const status = d?.metadata?.statusCode ?? null;
72 + const ok = Boolean(r.success && d && (status === null || status < 400));
73 + return {
74 + success: ok,
75 + engine: 'firecrawl',
76 + url,
77 + finalUrl: d?.metadata?.url ?? d?.metadata?.sourceURL ?? null,
78 + httpStatus: status,
79 + html: d?.html ?? d?.rawHtml ?? null,
80 + markdown: d?.markdown ?? null,
81 + json: d?.json ?? null,
82 + qualityScore: 0,
83 + requiresReview: false,
84 + error: ok ? null : (r.error ?? d?.metadata?.error ?? `status ${status}`),
85 + costCredits: r.creditsUsed ?? d?.metadata?.creditsUsed ?? 1,
86 + durationMs: Date.now() - started,
87 + fetchedAt: new Date(),
88 + };
89 + } catch (err) {
90 + return { success: false, engine: 'firecrawl', url, finalUrl: null, httpStatus: null, html: null, markdown: null, json: null, qualityScore: 0, requiresReview: false, error: err instanceof Error ? err.message : String(err), costCredits: 0, durationMs: Date.now() - started, fetchedAt: new Date() };
91 + }
92 + }
93 +
94 + async function search(query: string, o: { limit?: number; scrape?: boolean; country?: string; tbs?: string } = {}) {
95 + const body: Record<string, unknown> = { query, limit: o.limit ?? 10, ...(o.country ? { country: o.country } : {}), ...(o.tbs ? { tbs: o.tbs } : {}) };
96 + if (o.scrape) body.scrapeOptions = { formats: ['markdown'] };
97 + const r = await post<{ success: boolean; data?: { web?: Array<{ url: string; title?: string; description?: string; markdown?: string }> } | Array<{ url: string; title?: string; description?: string; markdown?: string }>; creditsUsed?: number }>('/v2/search', body, 60_000);
98 + const data = r.data;
99 + const items = Array.isArray(data) ? data : (data?.web ?? []);
100 + return { items, credits: r.creditsUsed ?? items.length };
101 + }
102 +
103 + async function map(url: string, o: { search?: string; limit?: number } = {}) {
104 + const r = await post<{ success: boolean; links?: Array<string | { url: string }>; creditsUsed?: number }>('/v2/map', { url, limit: o.limit ?? 5000, ...(o.search ? { search: o.search } : {}) }, 60_000);
105 + const links = (r.links ?? []).map((l) => (typeof l === 'string' ? l : l.url));
106 + return { links, credits: r.creditsUsed ?? 1 };
107 + }
108 +
109 + return { scrape, search, map };
110 +}
111 +
112 +export type FirecrawlEngine = ReturnType<typeof createFirecrawlEngine>;
added packages/connectors/src/engines/http.ts +100 −0
@@ -0,0 +1,100 @@
1 +import type { ExtractionResult } from '@rareindex/shared';
2 +import type { FetchOptions } from '../types.js';
3 +
4 +const DEFAULT_UA = 'RareIndexBot/0.1 (+https://www.rareindex.io/about/data; market-data research; contact: data@rareindex.io)';
5 +
6 +export interface HttpEngineOptions {
7 + userAgent?: string;
8 + defaultTimeoutMs?: number;
9 + maxRetries?: number;
10 +}
11 +
12 +/**
13 + * Direct HTTP engine for official APIs, feeds and plain public pages. Honest user agent, no
14 + * evasion, exponential backoff on 429/5xx, and conditional requests when the caller passes
15 + * If-None-Match / If-Modified-Since headers (budget engine).
16 + */
17 +export function createHttpEngine(opts: HttpEngineOptions = {}) {
18 + const ua = opts.userAgent ?? DEFAULT_UA;
19 + const maxRetries = opts.maxRetries ?? 3;
20 +
21 + async function fetchUrl(url: string, o: FetchOptions = {}): Promise<ExtractionResult> {
22 + const started = Date.now();
23 + const timeoutMs = o.timeoutMs ?? opts.defaultTimeoutMs ?? 30_000;
24 + let attempt = 0;
25 + let lastErr: string | null = null;
26 + while (attempt <= maxRetries) {
27 + attempt++;
28 + const ctrl = new AbortController();
29 + const timer = setTimeout(() => ctrl.abort(), timeoutMs);
30 + try {
31 + const res = await fetch(url, {
32 + method: o.method ?? 'GET',
33 + headers: {
34 + 'user-agent': ua,
35 + accept: o.responseType === 'text' ? 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' : 'application/json, text/plain, */*',
36 + 'accept-language': 'en-US,en;q=0.9',
37 + ...(o.body !== undefined ? { 'content-type': 'application/json' } : {}),
38 + ...(o.headers ?? {}),
39 + },
40 + body: o.body !== undefined ? JSON.stringify(o.body) : undefined,
41 + signal: ctrl.signal,
42 + redirect: 'follow',
43 + });
44 + clearTimeout(timer);
45 + const status = res.status;
46 + if (status === 304) {
47 + return { success: true, engine: 'api', url, finalUrl: res.url, httpStatus: 304, html: null, markdown: null, json: null, qualityScore: 1, requiresReview: false, error: null, costCredits: 0, durationMs: Date.now() - started, fetchedAt: new Date() };
48 + }
49 + if ((status === 429 || status >= 500) && attempt <= maxRetries) {
50 + const retryAfter = Number(res.headers.get('retry-after'));
51 + const wait = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 1000 * 2 ** attempt);
52 + lastErr = `HTTP ${status}`;
53 + await new Promise((r) => setTimeout(r, wait));
54 + continue;
55 + }
56 + const ctype = res.headers.get('content-type') ?? '';
57 + const isJson = o.responseType !== 'text' && (ctype.includes('json') || o.responseType === 'json');
58 + let json: unknown = null;
59 + let text: string | null = null;
60 + if (isJson) {
61 + const raw = await res.text();
62 + try {
63 + json = raw ? JSON.parse(raw) : null;
64 + } catch {
65 + text = raw;
66 + }
67 + } else {
68 + text = await res.text();
69 + }
70 + const ok = res.ok && (o.failOnHttpError === false || status < 400);
71 + return {
72 + success: ok,
73 + engine: 'api',
74 + url,
75 + finalUrl: res.url,
76 + httpStatus: status,
77 + html: text,
78 + markdown: null,
79 + json,
80 + qualityScore: ok ? 1 : 0,
81 + requiresReview: false,
82 + error: ok ? null : `HTTP ${status}`,
83 + costCredits: 0,
84 + durationMs: Date.now() - started,
85 + fetchedAt: new Date(),
86 + };
87 + } catch (err) {
88 + clearTimeout(timer);
89 + lastErr = err instanceof Error ? err.message : String(err);
90 + if (attempt > maxRetries) break;
91 + await new Promise((r) => setTimeout(r, Math.min(15_000, 500 * 2 ** attempt)));
92 + }
93 + }
94 + return { success: false, engine: 'api', url, finalUrl: null, httpStatus: null, html: null, markdown: null, json: null, qualityScore: 0, requiresReview: false, error: lastErr ?? 'fetch failed', costCredits: 0, durationMs: Date.now() - started, fetchedAt: new Date() };
95 + }
96 +
97 + return { fetch: fetchUrl };
98 +}
99 +
100 +export type HttpEngine = ReturnType<typeof createHttpEngine>;
added packages/connectors/src/engines/scrapfly.ts +113 −0
@@ -0,0 +1,113 @@
1 +import type { ExtractionResult } from '@rareindex/shared';
2 +import type { FetchOptions } from '../types.js';
3 +
4 +export interface ScrapflyEngineOptions {
5 + apiKey: string;
6 + baseUrl?: string;
7 + defaultCountry?: string;
8 + defaultTimeoutMs?: number;
9 +}
10 +
11 +interface ScrapflyResponse {
12 + result?: {
13 + content?: string;
14 + status_code?: number;
15 + url?: string;
16 + success?: boolean;
17 + error?: { message?: string; code?: string } | null;
18 + format?: string;
19 + extracted_data?: { data?: unknown } | unknown;
20 + };
21 + context?: { cost?: { total?: number } };
22 + config?: Record<string, unknown>;
23 + cost?: number;
24 + message?: string;
25 +}
26 +
27 +/**
28 + * Scrapfly REST client (§104): secondary engine for JS-heavy or difficult public pages, with
29 + * geo routing and rendering. `asp` (anti-scraping protection) is only used on publicly accessible
30 + * pages within the source's terms — never to defeat logins, paywalls or CAPTCHAs (§179).
31 + */
32 +export function createScrapflyEngine(opts: ScrapflyEngineOptions) {
33 + const base = (opts.baseUrl ?? 'https://api.scrapfly.io').replace(/\/$/, '');
34 +
35 + async function scrape(url: string, o: FetchOptions = {}): Promise<ExtractionResult> {
36 + const started = Date.now();
37 + const timeoutMs = o.timeoutMs ?? opts.defaultTimeoutMs ?? 90_000;
38 + const params = new URLSearchParams({
39 + key: opts.apiKey,
40 + url,
41 + asp: 'true',
42 + render_js: o.renderJs === false ? 'false' : 'true',
43 + country: (o.country ?? opts.defaultCountry ?? 'us').toLowerCase(),
44 + retry: 'true',
45 + format: 'raw',
46 + });
47 + if (o.waitForMs) params.set('rendering_wait', String(Math.min(o.waitForMs, 25_000)));
48 + if (o.extractionModel) params.set('extraction_model', o.extractionModel);
49 + if (o.headers) for (const [k, v] of Object.entries(o.headers)) params.append(`headers[${k}]`, v);
50 + if (o.method === 'POST') params.set('method', 'POST');
51 + const ctrl = new AbortController();
52 + const timer = setTimeout(() => ctrl.abort(), timeoutMs + 5_000);
53 + try {
54 + const res = await fetch(`${base}/scrape?${params.toString()}`, {
55 + method: o.method === 'POST' ? 'POST' : 'GET',
56 + body: o.method === 'POST' && o.body !== undefined ? JSON.stringify(o.body) : undefined,
57 + headers: o.method === 'POST' ? { 'content-type': 'application/json' } : undefined,
58 + signal: ctrl.signal,
59 + });
60 + const text = await res.text();
61 + let json: ScrapflyResponse | null = null;
62 + try {
63 + json = JSON.parse(text) as ScrapflyResponse;
64 + } catch {
65 + return fail(`scrapfly HTTP ${res.status}: ${text.slice(0, 200)}`, started, url);
66 + }
67 + const r = json.result;
68 + const status = r?.status_code ?? null;
69 + const cost = json.context?.cost?.total ?? json.cost ?? 1;
70 + if (!res.ok || !r) return fail(`scrapfly HTTP ${res.status}: ${json.message ?? r?.error?.message ?? 'no result'}`, started, url, cost);
71 + const ok = Boolean(r.success !== false && (status === null || status < 400));
72 + const content = r.content ?? null;
73 + const extracted = (r.extracted_data as { data?: unknown } | undefined)?.data ?? r.extracted_data ?? null;
74 + const looksJson = content && /^\s*[[{]/.test(content);
75 + let parsed: unknown = extracted;
76 + if (!parsed && looksJson) {
77 + try {
78 + parsed = JSON.parse(content!);
79 + } catch {
80 + parsed = null;
81 + }
82 + }
83 + return {
84 + success: ok,
85 + engine: 'scrapfly',
86 + url,
87 + finalUrl: r.url ?? null,
88 + httpStatus: status,
89 + html: content,
90 + markdown: null,
91 + json: parsed,
92 + qualityScore: 0,
93 + requiresReview: false,
94 + error: ok ? null : (r.error?.message ?? `status ${status}`),
95 + costCredits: cost,
96 + durationMs: Date.now() - started,
97 + fetchedAt: new Date(),
98 + };
99 + } catch (err) {
100 + return fail(err instanceof Error ? err.message : String(err), started, url);
101 + } finally {
102 + clearTimeout(timer);
103 + }
104 + }
105 +
106 + function fail(error: string, started: number, url: string, cost = 0): ExtractionResult {
107 + return { success: false, engine: 'scrapfly', url, finalUrl: null, httpStatus: null, html: null, markdown: null, json: null, qualityScore: 0, requiresReview: false, error, costCredits: cost, durationMs: Date.now() - started, fetchedAt: new Date() };
108 + }
109 +
110 + return { scrape };
111 +}
112 +
113 +export type ScrapflyEngine = ReturnType<typeof createScrapflyEngine>;
added packages/connectors/src/health.ts +88 −0
@@ -0,0 +1,88 @@
1 +import type { ConnectorHealth } from '@rareindex/shared';
2 +import type { HealthContext } from './types.js';
3 +
4 +/**
5 + * Derive a ConnectorHealth snapshot (§105) from recent runs. Anomaly detectors that need data
6 + * distributions (price drift, duplicate explosion) run in the normalizer and are passed in as
7 + * run anomalies.
8 + */
9 +export function healthFromRuns(ctx: HealthContext, schemaVersion: string): ConnectorHealth {
10 + const now = Date.now();
11 + const runs24h = ctx.recentRuns.filter((r) => now - r.startedAt.getTime() <= 24 * 3600_000);
12 + const attempted = runs24h.reduce((a, r) => a + r.pagesAttempted, 0);
13 + const success = runs24h.reduce((a, r) => a + r.pagesSuccess, 0);
14 + const rawRecords = runs24h.reduce((a, r) => a + r.recordsRaw, 0);
15 + const dupes = runs24h.reduce((a, r) => a + r.recordsDuplicate, 0);
16 + const fc = sumEngine(runs24h, 'firecrawl');
17 + const sf = sumEngine(runs24h, 'scrapfly');
18 + const lastOk = [...ctx.recentRuns].reverse().find((r) => r.status === 'success' || r.status === 'partial');
19 + const lastErr = [...ctx.recentRuns].reverse().find((r) => r.error)?.error ?? null;
20 + const anomalies = [...new Set(runs24h.flatMap((r) => r.anomalies))];
21 + const successRate = attempted > 0 ? success / attempted : null;
22 + const failing = ctx.recentRuns.slice(-3).length >= 3 && ctx.recentRuns.slice(-3).every((r) => r.status === 'failed');
23 +
24 + let status: ConnectorHealth['status'] = 'unknown';
25 + if (ctx.recentRuns.length === 0) status = 'unknown';
26 + else if (failing) status = 'failing';
27 + else if (successRate !== null && successRate < 0.5) status = 'failing';
28 + else if ((successRate !== null && successRate < 0.85) || anomalies.length > 0) status = 'degraded';
29 + else status = 'healthy';
30 +
31 + return {
32 + connector: ctx.meta.id,
33 + status,
34 + success_rate_24h: successRate === null ? null : round3(successRate),
35 + pages_attempted: attempted,
36 + pages_success: success,
37 + firecrawl_success_rate: fc.attempts ? round3(fc.success / fc.attempts) : null,
38 + scrapfly_fallback_rate: attempted ? round3(sf.attempts / attempted) : null,
39 + parse_failure_rate: rawRecords ? round3(runs24h.reduce((a, r) => a + (r.anomalies.filter((x) => x.startsWith('parse_failure')).length), 0) / rawRecords) : null,
40 + last_success: lastOk ? lastOk.startedAt.toISOString() : null,
41 + last_error: lastErr,
42 + schema_version: schemaVersion,
43 + records_24h: rawRecords,
44 + duplicates_24h: dupes,
45 + anomalies,
46 + };
47 +}
48 +
49 +function sumEngine(runs: HealthContext['recentRuns'], engine: string) {
50 + return runs.reduce(
51 + (acc, r) => {
52 + const s = r.engineStats[engine];
53 + if (s) {
54 + acc.attempts += s.attempts;
55 + acc.success += s.success;
56 + }
57 + return acc;
58 + },
59 + { attempts: 0, success: 0 },
60 + );
61 +}
62 +
63 +function round3(x: number): number {
64 + return Math.round(x * 1000) / 1000;
65 +}
66 +
67 +/**
68 + * Detect abnormal price distributions between a baseline and a new batch (§105): median shift
69 + * beyond a factor or a collapse of dispersion suggests a parser/currency problem.
70 + */
71 +export function priceDistributionAnomaly(baseline: number[], batch: number[], factor = 3): string | null {
72 + if (baseline.length < 20 || batch.length < 10) return null;
73 + const med = (xs: number[]) => {
74 + const s = [...xs].sort((a, b) => a - b);
75 + return s[Math.floor(s.length / 2)]!;
76 + };
77 + const b = med(baseline);
78 + const n = med(batch);
79 + if (b > 0 && (n / b > factor || b / n > factor)) return `price_distribution_shift: median ${b.toFixed(2)} → ${n.toFixed(2)}`;
80 + return null;
81 +}
82 +
83 +/** Duplicate explosion: share of duplicate raw records in a run is far above baseline. */
84 +export function duplicateExplosion(records: number, duplicates: number, threshold = 0.9): string | null {
85 + if (records < 50) return null;
86 + const share = duplicates / records;
87 + return share >= threshold ? `duplicate_explosion: ${(share * 100).toFixed(0)}% duplicates` : null;
88 +}
added packages/connectors/src/html.ts +87 −0
@@ -0,0 +1,87 @@
1 +import * as cheerio from 'cheerio';
2 +
3 +/** Thin HTML helpers shared by connectors (cheerio). */
4 +export function load(html: string) {
5 + return cheerio.load(html);
6 +}
7 +
8 +export type CheerioRoot = ReturnType<typeof cheerio.load>;
9 +
10 +export function text($el: { text(): string } | null | undefined): string | null {
11 + const t = $el?.text()?.replace(/\s+/g, ' ').trim();
12 + return t ? t : null;
13 +}
14 +
15 +export function absUrl(base: string, href: string | null | undefined): string | null {
16 + if (!href) return null;
17 + try {
18 + return new URL(href, base).toString();
19 + } catch {
20 + return null;
21 + }
22 +}
23 +
24 +/** Extract JSON-LD blocks of a given @type (e.g. Product, Offer) from HTML. */
25 +export function jsonLd(html: string, type?: string): Record<string, unknown>[] {
26 + const $ = cheerio.load(html);
27 + const out: Record<string, unknown>[] = [];
28 + $('script[type="application/ld+json"]').each((_, el) => {
29 + const raw = $(el).contents().text();
30 + try {
31 + const parsed = JSON.parse(raw) as unknown;
32 + const items = Array.isArray(parsed) ? parsed : (parsed && typeof parsed === 'object' && '@graph' in (parsed as object) ? ((parsed as { '@graph': unknown[] })['@graph'] ?? []) : [parsed]);
33 + for (const it of items) {
34 + if (it && typeof it === 'object') {
35 + const t = (it as { '@type'?: string | string[] })['@type'];
36 + const types = Array.isArray(t) ? t : t ? [t] : [];
37 + if (!type || types.includes(type)) out.push(it as Record<string, unknown>);
38 + }
39 + }
40 + } catch {
41 + /* ignore malformed JSON-LD */
42 + }
43 + });
44 + return out;
45 +}
46 +
47 +/** Extract Next.js __NEXT_DATA__ payload when present. */
48 +export function nextData(html: string): unknown | null {
49 + const m = html.match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
50 + if (!m) return null;
51 + try {
52 + return JSON.parse(m[1]!);
53 + } catch {
54 + return null;
55 + }
56 +}
57 +
58 +/** Extract a JS object literal assigned to a global, e.g. window.__INITIAL_STATE__ = {...}; */
59 +export function inlineJson(html: string, marker: string): unknown | null {
60 + const idx = html.indexOf(marker);
61 + if (idx < 0) return null;
62 + const start = html.indexOf('{', idx);
63 + if (start < 0) return null;
64 + let depth = 0;
65 + let inStr: string | null = null;
66 + for (let i = start; i < html.length; i++) {
67 + const ch = html[i]!;
68 + if (inStr) {
69 + if (ch === '\\') i++;
70 + else if (ch === inStr) inStr = null;
71 + continue;
72 + }
73 + if (ch === '"' || ch === "'") inStr = ch;
74 + else if (ch === '{') depth++;
75 + else if (ch === '}') {
76 + depth--;
77 + if (depth === 0) {
78 + try {
79 + return JSON.parse(html.slice(start, i + 1));
80 + } catch {
81 + return null;
82 + }
83 + }
84 + }
85 + }
86 + return null;
87 +}
added packages/connectors/src/index.ts +11 −0
@@ -0,0 +1,11 @@
1 +export * from './types.js';
2 +export * from './quality.js';
3 +export * from './router.js';
4 +export * from './health.js';
5 +export * from './registry.js';
6 +export * from './base.js';
7 +export * from './context.js';
8 +export * as html from './html.js';
9 +export { createHttpEngine } from './engines/http.js';
10 +export { createFirecrawlEngine } from './engines/firecrawl.js';
11 +export { createScrapflyEngine } from './engines/scrapfly.js';
added packages/connectors/src/quality.test.ts +34 −0
@@ -0,0 +1,34 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { qualityScore, documentQuality } from './quality.js';
3 +import { healthFromRuns } from './health.js';
4 +import { childLogger } from '@rareindex/shared';
5 +import type { ConnectorMeta } from './types.js';
6 +
7 +const meta = { id: 'x', schemaVersion: '1.0' } as unknown as ConnectorMeta;
8 +
9 +describe('quality', () => {
10 + it('scores expected fields', () => {
11 + expect(qualityScore({ title: 'a', price: 1, status: 'sold', date: new Date(), images: ['u'], description: 'd', identifiers: { a: 'b' }, category: 'c', currency: 'USD' })).toBe(1);
12 + expect(qualityScore({ title: 'a' }, ['title', 'price'])).toBe(0.5);
13 + expect(qualityScore(null)).toBe(0);
14 + });
15 + it('flags blocked documents', () => {
16 + expect(documentQuality({ html: 'Just a moment... Cloudflare', httpStatus: 200 })).toBeLessThan(0.5);
17 + expect(documentQuality({ html: 'x'.repeat(5000), httpStatus: 200 })).toBeGreaterThanOrEqual(0.8);
18 + expect(documentQuality({ html: 'x'.repeat(5000), httpStatus: 403 })).toBe(0);
19 + });
20 + it('derives health', () => {
21 + const h = healthFromRuns(
22 + {
23 + meta,
24 + log: childLogger({}),
25 + fetch: async () => ({}) as never,
26 + recentRuns: [{ startedAt: new Date(), status: 'success', pagesAttempted: 100, pagesSuccess: 98, recordsRaw: 500, recordsDuplicate: 20, engineStats: { firecrawl: { attempts: 100, success: 91, credits: 100, ms: 1 }, scrapfly: { attempts: 7, success: 7, credits: 7, ms: 1 } }, anomalies: [], error: null }],
27 + },
28 + '1.0',
29 + );
30 + expect(h.status).toBe('healthy');
31 + expect(h.success_rate_24h).toBe(0.98);
32 + expect(h.scrapfly_fallback_rate).toBe(0.07);
33 + });
34 +});
added packages/connectors/src/quality.ts +58 −0
@@ -0,0 +1,58 @@
1 +import type { QualityField } from './types.js';
2 +
3 +/** Extraction quality weights (§199). */
4 +export const QUALITY_WEIGHTS: Record<QualityField, number> = {
5 + title: 0.2,
6 + price: 0.2,
7 + status: 0.15,
8 + date: 0.1,
9 + images: 0.1,
10 + description: 0.05,
11 + identifiers: 0.1,
12 + category: 0.05,
13 + currency: 0.05,
14 +};
15 +
16 +export const MIN_QUALITY = 0.6;
17 +
18 +function present(v: unknown): boolean {
19 + if (v === null || v === undefined) return false;
20 + if (typeof v === 'string') return v.trim().length > 0;
21 + if (Array.isArray(v)) return v.length > 0;
22 + if (typeof v === 'number') return Number.isFinite(v);
23 + if (v instanceof Date) return !Number.isNaN(v.getTime());
24 + if (typeof v === 'object') return Object.keys(v as object).length > 0;
25 + return Boolean(v);
26 +}
27 +
28 +/**
29 + * Score 0–1 = weighted share of expected fields that were found. When `expect` is omitted all
30 + * weighted fields count; otherwise weights are renormalised over the expected subset so that a
31 + * catalog page (no price/status) can still reach 1.0.
32 + */
33 +export function qualityScore(found: Partial<Record<QualityField, unknown>> | null | undefined, expect?: QualityField[]): number {
34 + if (!found) return 0;
35 + const fields = (expect && expect.length ? expect : (Object.keys(QUALITY_WEIGHTS) as QualityField[]));
36 + let total = 0;
37 + let got = 0;
38 + for (const f of fields) {
39 + const w = QUALITY_WEIGHTS[f] ?? 0;
40 + total += w;
41 + if (present(found[f])) got += w;
42 + }
43 + return total > 0 ? Math.round((got / total) * 1000) / 1000 : 0;
44 +}
45 +
46 +/** Heuristic score for a document when the connector provides no parser: did we get real content? */
47 +export function documentQuality(doc: { html?: string | null; markdown?: string | null; json?: unknown; httpStatus?: number | null }): number {
48 + if (doc.httpStatus && (doc.httpStatus >= 400 || doc.httpStatus === 0)) return 0;
49 + if (doc.json && present(doc.json)) return 0.9;
50 + const text = doc.markdown ?? doc.html ?? '';
51 + if (!text) return 0;
52 + const len = text.length;
53 + const blocked = /access denied|captcha|verify you are human|attention required|cloudflare|just a moment|request blocked|unusual traffic|enable javascript and cookies/i.test(text.slice(0, 4000));
54 + if (blocked && len < 20_000) return 0.1;
55 + if (len < 500) return 0.2;
56 + if (len < 2_000) return 0.5;
57 + return 0.8;
58 +}
added packages/connectors/src/registry.ts +62 −0
@@ -0,0 +1,62 @@
1 +import { readFileSync } from 'node:fs';
2 +import path from 'node:path';
3 +import { fileURLToPath } from 'node:url';
4 +import { RegistrySchema, type ConnectorMeta, type Registry, type RareIndexConnector } from './types.js';
5 +
6 +const here = path.dirname(fileURLToPath(import.meta.url));
7 +export const CONNECTORS_DIR = path.resolve(here, '../../../connectors');
8 +export const REGISTRY_PATH = path.join(CONNECTORS_DIR, 'registry.json');
9 +
10 +let cached: Registry | null = null;
11 +
12 +/** Load and validate connectors/registry.json (§106). */
13 +export function loadRegistry(force = false): Registry {
14 + if (cached && !force) return cached;
15 + const raw = JSON.parse(readFileSync(REGISTRY_PATH, 'utf8'));
16 + cached = RegistrySchema.parse(raw);
17 + const ids = new Set<string>();
18 + for (const c of cached.connectors) {
19 + if (ids.has(c.id)) throw new Error(`registry: duplicate connector id ${c.id}`);
20 + ids.add(c.id);
21 + }
22 + return cached;
23 +}
24 +
25 +export function getConnectorMeta(id: string): ConnectorMeta {
26 + const meta = loadRegistry().connectors.find((c) => c.id === id);
27 + if (!meta) throw new Error(`Unknown connector: ${id}`);
28 + return meta;
29 +}
30 +
31 +export function listConnectorMeta(filter: { enabled?: boolean; category?: string; sourceType?: string } = {}): ConnectorMeta[] {
32 + return loadRegistry().connectors.filter((c) => {
33 + if (filter.enabled !== undefined && c.enabled !== filter.enabled) return false;
34 + if (filter.category && !c.categories.includes(filter.category)) return false;
35 + if (filter.sourceType && c.sourceType !== filter.sourceType) return false;
36 + return true;
37 + });
38 +}
39 +
40 +/**
41 + * Dynamically import a connector module. Each module default-exports a factory
42 + * `(meta: ConnectorMeta) => RareIndexConnector`.
43 + */
44 +export async function loadConnector(id: string): Promise<RareIndexConnector> {
45 + const meta = getConnectorMeta(id);
46 + const modPath = path.join(CONNECTORS_DIR, meta.module, 'index.ts');
47 + const mod = (await import(modPath)) as { default?: (meta: ConnectorMeta) => RareIndexConnector | Promise<RareIndexConnector>; createConnector?: (meta: ConnectorMeta) => RareIndexConnector | Promise<RareIndexConnector> };
48 + const factory = mod.default ?? mod.createConnector;
49 + if (!factory) throw new Error(`connector module ${meta.module} must default-export a factory`);
50 + return factory(meta);
51 +}
52 +
53 +/** Find connectors whose urlPatterns match a URL (scanner paste-URL, §114). */
54 +export async function connectorsForUrl(url: string): Promise<RareIndexConnector[]> {
55 + const out: RareIndexConnector[] = [];
56 + for (const meta of listConnectorMeta({ enabled: true })) {
57 + if (!meta.supportsLookup) continue;
58 + const c = await loadConnector(meta.id);
59 + if (c.urlPatterns?.some((re) => re.test(url))) out.push(c);
60 + }
61 + return out;
62 +}
added packages/connectors/src/router.ts +101 −0
@@ -0,0 +1,101 @@
1 +import type { Engine, ExtractionResult, Logger } from '@rareindex/shared';
2 +import { createHttpEngine, type HttpEngine } from './engines/http.js';
3 +import { createFirecrawlEngine, type FirecrawlEngine } from './engines/firecrawl.js';
4 +import { createScrapflyEngine, type ScrapflyEngine } from './engines/scrapfly.js';
5 +import { documentQuality, qualityScore, MIN_QUALITY } from './quality.js';
6 +import type { EngineStats, FetchOptions } from './types.js';
7 +
8 +export interface RouterOptions {
9 + firecrawlApiKey?: string;
10 + scrapflyApiKey?: string;
11 + log?: Logger;
12 + /** called for every engine attempt (cost ledger, metrics) */
13 + onAttempt?: (info: { engine: Engine; url: string; success: boolean; credits: number; ms: number; quality: number; error: string | null }) => void;
14 +}
15 +
16 +export interface Router {
17 + fetch(url: string, opts?: FetchOptions, defaults?: { enginePriority: Engine[]; stats?: Record<string, EngineStats> }): Promise<ExtractionResult>;
18 + engines: { http: HttpEngine; firecrawl: FirecrawlEngine | null; scrapfly: ScrapflyEngine | null };
19 + available(): Engine[];
20 +}
21 +
22 +/**
23 + * Firecrawl → Scrapfly router (§198). Tries engines in priority order, scores each result with the
24 + * connector's parser (or a document heuristic), and falls through until quality ≥ minQuality.
25 + * A result that fails everywhere is returned with requiresReview=true — never silently dropped.
26 + */
27 +export function createRouter(opts: RouterOptions = {}): Router {
28 + const http = createHttpEngine();
29 + const firecrawl = opts.firecrawlApiKey ? createFirecrawlEngine({ apiKey: opts.firecrawlApiKey }) : null;
30 + const scrapfly = opts.scrapflyApiKey ? createScrapflyEngine({ apiKey: opts.scrapflyApiKey }) : null;
31 +
32 + function available(): Engine[] {
33 + const out: Engine[] = ['api', 'feed'];
34 + if (firecrawl) out.push('firecrawl');
35 + if (scrapfly) out.push('scrapfly');
36 + return out;
37 + }
38 +
39 + async function runEngine(engine: Engine, url: string, o: FetchOptions): Promise<ExtractionResult | null> {
40 + switch (engine) {
41 + case 'api':
42 + case 'feed':
43 + return http.fetch(url, o);
44 + case 'firecrawl':
45 + return firecrawl ? firecrawl.scrape(url, o) : null;
46 + case 'scrapfly':
47 + return scrapfly ? scrapfly.scrape(url, o) : null;
48 + default:
49 + return null;
50 + }
51 + }
52 +
53 + async function routedFetch(url: string, o: FetchOptions = {}, defaults?: { enginePriority: Engine[]; stats?: Record<string, EngineStats> }): Promise<ExtractionResult> {
54 + const engines = (o.engines ?? defaults?.enginePriority ?? ['api', 'firecrawl', 'scrapfly']).filter((e) => available().includes(e));
55 + const minQuality = o.minQuality ?? MIN_QUALITY;
56 + let last: ExtractionResult | null = null;
57 + const errors: string[] = [];
58 + for (const engine of engines) {
59 + const res = await runEngine(engine, url, o);
60 + if (!res) continue;
61 + let quality = 0;
62 + if (res.success) {
63 + if (o.parse) {
64 + try {
65 + const found = o.parse(res);
66 + quality = qualityScore(found, o.expect);
67 + } catch (err) {
68 + quality = 0;
69 + res.error = `parse: ${err instanceof Error ? err.message : String(err)}`;
70 + }
71 + } else {
72 + quality = documentQuality(res);
73 + }
74 + }
75 + res.qualityScore = quality;
76 + const stats = defaults?.stats;
77 + if (stats) {
78 + const s = (stats[engine] ??= { attempts: 0, success: 0, credits: 0, ms: 0 });
79 + s.attempts++;
80 + s.credits += res.costCredits ?? 0;
81 + s.ms += res.durationMs ?? 0;
82 + if (res.success && quality >= minQuality) s.success++;
83 + }
84 + opts.onAttempt?.({ engine, url, success: res.success && quality >= minQuality, credits: res.costCredits ?? 0, ms: res.durationMs ?? 0, quality, error: res.error });
85 + if (res.success && quality >= minQuality) return res;
86 + errors.push(`${engine}: ${res.error ?? `quality ${quality}`}`);
87 + last = res;
88 + opts.log?.debug({ url, engine, quality, error: res.error }, 'engine fallback');
89 + // Do not escalate on hard "not found"/"gone" statuses: the page does not exist.
90 + if (res.httpStatus === 404 || res.httpStatus === 410) break;
91 + }
92 + return {
93 + ...(last ?? { engine: engines[0] ?? 'api', url, finalUrl: null, httpStatus: null, html: null, markdown: null, json: null, costCredits: 0, durationMs: 0, fetchedAt: new Date(), qualityScore: 0 }),
94 + success: false,
95 + requiresReview: true,
96 + error: errors.join(' | ') || 'no engine available',
97 + };
98 + }
99 +
100 + return { fetch: routedFetch, engines: { http, firecrawl, scrapfly }, available };
101 +}
added packages/connectors/src/testing.ts +133 −0
@@ -0,0 +1,133 @@
1 +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2 +import path from 'node:path';
3 +import { fileURLToPath } from 'node:url';
4 +import { NormalizedRecordSchema, type NormalizedRecord } from '@rareindex/shared';
5 +import type { RareIndexConnector, RawRecordLike } from './types.js';
6 +
7 +const here = path.dirname(fileURLToPath(import.meta.url));
8 +export const FIXTURES_DIR = path.resolve(here, '../../../data/fixtures');
9 +
10 +/**
11 + * Fixture conventions (§146): data/fixtures/<connectorId>/<name>.json holds a captured RawRecordLike
12 + * (`{ url, externalId, kind, engine, fetchedAt, payload }`) plus optional `expect` block used by
13 + * the standard test-suite. Large HTML captures live next to it as <name>.html and are referenced
14 + * by payload.snapshot = "<name>.html".
15 + */
16 +export interface Fixture {
17 + raw: RawRecordLike;
18 + expect?: {
19 + count?: number;
20 + minCount?: number;
21 + kinds?: string[];
22 + first?: Partial<Record<string, unknown>>;
23 + /** every record must have these fields non-null */
24 + requiredFields?: string[];
25 + };
26 + capturedAt?: string;
27 + note?: string;
28 +}
29 +
30 +export function fixtureDir(connectorId: string): string {
31 + return path.join(FIXTURES_DIR, connectorId);
32 +}
33 +
34 +export function listFixtures(connectorId: string): string[] {
35 + const dir = fixtureDir(connectorId);
36 + if (!existsSync(dir)) return [];
37 + return readdirSync(dir).filter((f) => f.endsWith('.json')).map((f) => f.replace(/\.json$/, ''));
38 +}
39 +
40 +export function loadFixture(connectorId: string, name: string): Fixture {
41 + const dir = fixtureDir(connectorId);
42 + const f = JSON.parse(readFileSync(path.join(dir, `${name}.json`), 'utf8')) as Fixture;
43 + f.raw.fetchedAt = new Date(f.raw.fetchedAt);
44 + const p = f.raw.payload as { snapshot?: string } | null;
45 + if (p && typeof p === 'object' && typeof p.snapshot === 'string' && p.snapshot.endsWith('.html')) {
46 + p.snapshot = readFileSync(path.join(dir, p.snapshot), 'utf8');
47 + }
48 + return f;
49 +}
50 +
51 +export function saveFixture(connectorId: string, name: string, fixture: Fixture): void {
52 + const dir = fixtureDir(connectorId);
53 + mkdirSync(dir, { recursive: true });
54 + const p = fixture.raw.payload as { snapshot?: string } | null;
55 + if (p && typeof p === 'object' && typeof p.snapshot === 'string' && p.snapshot.length > 20_000) {
56 + writeFileSync(path.join(dir, `${name}.html`), p.snapshot);
57 + p.snapshot = `${name}.html`;
58 + }
59 + writeFileSync(path.join(dir, `${name}.json`), JSON.stringify({ ...fixture, capturedAt: fixture.capturedAt ?? new Date().toISOString() }, null, 2));
60 +}
61 +
62 +/** Validate normalised output against the canonical schemas. Throws with a readable message. */
63 +export function assertNormalized(records: NormalizedRecord[]): NormalizedRecord[] {
64 + return records.map((r, i) => {
65 + const parsed = NormalizedRecordSchema.safeParse(r);
66 + if (!parsed.success) throw new Error(`record[${i}] invalid: ${parsed.error.issues.map((x) => `${x.path.join('.')}: ${x.message}`).join('; ')}`);
67 + return parsed.data;
68 + });
69 +}
70 +
71 +/**
72 + * Standard fixture suite: run `normalize` on every fixture of a connector and check the common
73 + * invariants (schema validity, no crawl-time-as-sale-date, positive prices, currency present,
74 + * canonical URL, duplicate handling). Use from vitest:
75 + *
76 + * describe('scryfall', () => runFixtureSuite(connector, it, expect))
77 + */
78 +export function runFixtureSuite(connector: RareIndexConnector, it: (name: string, fn: () => Promise<void>) => void, expectFn: (v: unknown) => any): void {
79 + const names = listFixtures(connector.meta.id);
80 + it('has at least one fixture', async () => {
81 + expectFn(names.length).toBeGreaterThan(0);
82 + });
83 + for (const name of names) {
84 + it(`fixture ${name} normalises`, async () => {
85 + const fx = loadFixture(connector.meta.id, name);
86 + const out = assertNormalized(await connector.normalize(fx.raw));
87 + if (fx.expect?.count !== undefined) expectFn(out.length).toBe(fx.expect.count);
88 + if (fx.expect?.minCount !== undefined) expectFn(out.length).toBeGreaterThanOrEqual(fx.expect.minCount);
89 + if (fx.expect?.kinds) for (const r of out) expectFn(fx.expect.kinds).toContain(r.kind);
90 + const seen = new Set<string>();
91 + for (const r of out) {
92 + expectFn(r.sourceUrl.startsWith('http')).toBe(true);
93 + expectFn(r.connectorId).toBe(connector.meta.id);
94 + if (r.kind === 'sale') {
95 + expectFn(r.price).toBeGreaterThan(0);
96 + expectFn(r.currency).toBeTruthy();
97 + expectFn(r.saleDate.getTime()).toBeLessThanOrEqual(Date.now() + 86_400_000);
98 + // sale date must not simply equal the fetch time (§172)
99 + expectFn(Math.abs(r.saleDate.getTime() - fx.raw.fetchedAt.getTime()) > 1000 || r.saleDate.getUTCHours() === 0).toBe(true);
100 + }
101 + if (r.kind === 'listing' && r.price !== null) {
102 + expectFn(r.price).toBeGreaterThanOrEqual(0);
103 + expectFn(r.currency).toBeTruthy();
104 + }
105 + if (r.kind === 'price_observation') {
106 + expectFn(r.price).toBeGreaterThan(0);
107 + expectFn(r.currency).toBeTruthy();
108 + }
109 + if ('rawTitle' in r) expectFn(r.rawTitle.length).toBeGreaterThan(0);
110 + if ('attributes' in r) expectFn(r.attributes.categorySlug.length).toBeGreaterThan(0);
111 + const key = `${r.kind}|${'externalId' in r ? r.externalId : ''}|${r.sourceUrl}|${'price' in r ? r.price : ''}|${'saleDate' in r ? r.saleDate.toISOString() : ''}|${'priceKind' in r ? r.priceKind : ''}`;
112 + expectFn(seen.has(key)).toBe(false);
113 + seen.add(key);
114 + }
115 + if (fx.expect?.first) {
116 + const first = out[0] as unknown as Record<string, unknown>;
117 + for (const [k, v] of Object.entries(fx.expect.first)) {
118 + const actual = k.includes('.') ? k.split('.').reduce<unknown>((acc, p) => (acc && typeof acc === 'object' ? (acc as Record<string, unknown>)[p] : undefined), first) : first[k];
119 + expectFn(actual).toEqual(v);
120 + }
121 + }
122 + if (fx.expect?.requiredFields) {
123 + for (const r of out) {
124 + const rec = r as unknown as Record<string, unknown>;
125 + for (const f of fx.expect.requiredFields) {
126 + const val = f.includes('.') ? f.split('.').reduce<unknown>((acc, p) => (acc && typeof acc === 'object' ? (acc as Record<string, unknown>)[p] : undefined), rec) : rec[f];
127 + expectFn(val === null || val === undefined || val === '').toBe(false);
128 + }
129 + }
130 + }
131 + });
132 + }
133 +}
added packages/connectors/src/types.ts +165 −0
@@ -0,0 +1,165 @@
1 +import { z } from 'zod';
2 +import type { Logger } from '@rareindex/shared';
3 +import { EngineSchema, SourceTypeSchema, type Engine, type ExtractionResult, type NormalizedRecord, type RawRecord, type ConnectorHealth, type RecordKind } from '@rareindex/shared';
4 +
5 +/** Registry entry — mirrors connectors/registry.json (§106). */
6 +export const ConnectorMetaSchema = z.object({
7 + id: z.string().regex(/^[a-z0-9-]+$/),
8 + displayName: z.string(),
9 + sourceId: z.string(),
10 + sourceName: z.string(),
11 + sourceType: SourceTypeSchema,
12 + sourceUrl: z.string().url(),
13 + /** module path relative to /connectors, e.g. "api/scryfall" */
14 + module: z.string(),
15 + enginePriority: z.array(EngineSchema).min(1),
16 + categories: z.array(z.string()).min(1),
17 + regions: z.array(z.string()).default([]),
18 + languages: z.array(z.string()).default(['en']),
19 + currency: z.array(z.string()).default(['USD']),
20 + supportsListings: z.boolean().default(false),
21 + supportsSold: z.boolean().default(false),
22 + supportsAuctions: z.boolean().default(false),
23 + supportsImages: z.boolean().default(true),
24 + supportsCatalog: z.boolean().default(false),
25 + supportsPopulation: z.boolean().default(false),
26 + supportsLookup: z.boolean().default(false),
27 + refreshFrequencyMinutes: z.number().int().positive().default(1440),
28 + priority: z.enum(['high', 'medium', 'low']).default('medium'),
29 + /** 0–1 baseline trust for the source (§143) */
30 + trustScore: z.number().min(0).max(1).default(0.6),
31 + attributionRequired: z.boolean().default(true),
32 + termsUrl: z.string().optional(),
33 + /** legal/ethical notes: what we do and do not fetch from this source */
34 + accessNotes: z.string().optional(),
35 + enabled: z.boolean().default(true),
36 + schemaVersion: z.string().default('1.0'),
37 + config: z.record(z.string(), z.unknown()).default({}),
38 +});
39 +export type ConnectorMeta = z.infer<typeof ConnectorMetaSchema>;
40 +
41 +export const RegistrySchema = z.object({
42 + version: z.string(),
43 + connectors: z.array(ConnectorMetaSchema),
44 +});
45 +export type Registry = z.infer<typeof RegistrySchema>;
46 +
47 +/** What a connector yields while crawling; the worker adds ids, run id and persistence. */
48 +export interface RawRecordInput {
49 + url: string;
50 + externalId?: string | null;
51 + kind: RecordKind;
52 + engine: Engine;
53 + httpStatus?: number | null;
54 + payload: unknown;
55 + /** optional large text (HTML/markdown) persisted to disk, not the DB */
56 + snapshot?: string | null;
57 + fetchedAt?: Date;
58 +}
59 +
60 +export type QualityField = 'title' | 'price' | 'status' | 'date' | 'images' | 'description' | 'identifiers' | 'category' | 'currency';
61 +
62 +export interface FetchOptions {
63 + /** Restrict/override the engines to try, in order. Defaults to meta.enginePriority. */
64 + engines?: Engine[];
65 + /** Fields the parser is expected to find; drives the quality score (§199). */
66 + expect?: QualityField[];
67 + /** Parse the fetched document; return the fields found so the router can score quality. */
68 + parse?: (res: ExtractionResult) => Partial<Record<QualityField, unknown>> | null;
69 + /** Minimum quality to accept an engine result before falling back (default 0.6). */
70 + minQuality?: number;
71 + /** Engine hints */
72 + renderJs?: boolean;
73 + waitForMs?: number;
74 + country?: string;
75 + headers?: Record<string, string>;
76 + /** Firecrawl JSON extraction schema (when using the json format) */
77 + jsonSchema?: Record<string, unknown>;
78 + jsonPrompt?: string;
79 + /** Scrapfly extraction model (e.g. "product") */
80 + extractionModel?: string;
81 + timeoutMs?: number;
82 + /** skip cache/budget checks */
83 + force?: boolean;
84 + /** treat non-2xx as failure (default true) */
85 + failOnHttpError?: boolean;
86 + /** Body for POST requests (api engine) */
87 + method?: 'GET' | 'POST';
88 + body?: unknown;
89 + /** Return type for api engine: json (default) or text */
90 + responseType?: 'json' | 'text';
91 +}
92 +
93 +export interface CrawlOptions {
94 + mode: 'incremental' | 'backfill' | 'probe';
95 + /** soft cap on records to yield (probe/tests) */
96 + limit?: number;
97 + /** cursor persisted between runs (connector-defined shape) */
98 + cursor?: Record<string, unknown>;
99 + /** optional category filter (slugs) */
100 + categories?: string[];
101 + /** optional seed URLs/queries (manual runs) */
102 + seeds?: string[];
103 +}
104 +
105 +export interface EngineStats {
106 + attempts: number;
107 + success: number;
108 + credits: number;
109 + ms: number;
110 +}
111 +
112 +export interface CrawlContext {
113 + meta: ConnectorMeta;
114 + log: Logger;
115 + options: CrawlOptions;
116 + /** Routed fetch: api/http → firecrawl → scrapfly (§198). Records stats and cost. */
117 + fetch(url: string, opts?: FetchOptions): Promise<ExtractionResult>;
118 + /** Firecrawl search (when available) — returns urls + snippets/markdown. */
119 + search?(query: string, opts?: { limit?: number; scrape?: boolean; country?: string }): Promise<Array<{ url: string; title?: string; description?: string; markdown?: string }>>;
120 + /** Firecrawl map — discover URLs of a site. */
121 + map?(url: string, opts?: { search?: string; limit?: number }): Promise<string[]>;
122 + /** Crawl budget: true when the URL content changed since last time or is due (§170). */
123 + shouldFetch(url: string): Promise<boolean>;
124 + /** Persist an updated cursor (called by connector at checkpoints). */
125 + setCursor(cursor: Record<string, unknown>): Promise<void>;
126 + /** Report a non-fatal anomaly for health tracking (§105). */
127 + anomaly(kind: string, detail?: string): void;
128 + engineStats: Record<string, EngineStats>;
129 + /** Cooperative cancellation */
130 + signal?: AbortSignal;
131 +}
132 +
133 +export interface HealthContext {
134 + meta: ConnectorMeta;
135 + log: Logger;
136 + fetch(url: string, opts?: FetchOptions): Promise<ExtractionResult>;
137 + /** Recent run metrics supplied by the worker (may be empty for a fresh connector). */
138 + recentRuns: Array<{ startedAt: Date; status: string; pagesAttempted: number; pagesSuccess: number; recordsRaw: number; recordsDuplicate: number; engineStats: Record<string, EngineStats>; anomalies: string[]; error: string | null }>;
139 +}
140 +
141 +/**
142 + * The connector contract (§102). Every source implements exactly this. Everything else —
143 + * persistence, dedupe, entity resolution, valuation — is generic pipeline code.
144 + */
145 +export interface RareIndexConnector {
146 + readonly meta: ConnectorMeta;
147 + /** semantic version of connector code; bump when crawl logic changes */
148 + readonly version: string;
149 + /** version of the parsing/normalisation logic; bump when field mapping changes */
150 + readonly parserVersion: string;
151 + /** Yield raw records. Must be resumable through options.cursor. */
152 + crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput>;
153 + /** Pure function: raw payload → canonical normalised records. Must be deterministic and fixture-testable. */
154 + normalize(raw: RawRecordLike): Promise<NormalizedRecord[]>;
155 + /** Liveness/probe check. Base implementation derives status from recent runs and a light probe. */
156 + healthCheck(ctx: HealthContext): Promise<ConnectorHealth>;
157 + /** Optional: resolve a single source URL (scanner/paste URL, §114). */
158 + lookup?(url: string, ctx: CrawlContext): Promise<RawRecordInput[]>;
159 + /** Optional: URL patterns this connector understands (for lookup routing). */
160 + readonly urlPatterns?: RegExp[];
161 +}
162 +
163 +export type RawRecordLike = Pick<RawRecord, 'url' | 'externalId' | 'kind' | 'payload' | 'fetchedAt' | 'engine'> & { id?: string; connectorId?: string; sourceId?: string };
164 +
165 +export type ConnectorFactory = () => RareIndexConnector | Promise<RareIndexConnector>;
added packages/connectors/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": "src",
5 + "outDir": "dist",
6 + "noEmit": true
7 + },
8 + "include": ["src"]
9 +}
added packages/connectors/vitest.config.ts +5 −0
@@ -0,0 +1,5 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: { include: ['src/**/*.test.ts'] },
5 +});
added packages/database/drizzle.config.ts +10 −0
@@ -0,0 +1,10 @@
1 +import { defineConfig } from 'drizzle-kit';
2 +
3 +export default defineConfig({
4 + dialect: 'postgresql',
5 + schema: './src/schema/index.ts',
6 + out: './migrations',
7 + dbCredentials: { url: process.env.DATABASE_URL ?? 'postgres://localhost:5432/rareindex' },
8 + strict: true,
9 + verbose: true,
10 +});
added packages/database/migrations/0000_clear_unicorn.sql +941 −0
@@ -0,0 +1,941 @@
1 +CREATE TABLE "brands" (
2 + "slug" text PRIMARY KEY NOT NULL,
3 + "name" text NOT NULL,
4 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
5 + "country" text,
6 + "founded_year" integer,
7 + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
8 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
9 +);
10 +--> statement-breakpoint
11 +CREATE TABLE "categories" (
12 + "slug" text PRIMARY KEY NOT NULL,
13 + "parent_slug" text,
14 + "family_slug" text NOT NULL,
15 + "name" text NOT NULL,
16 + "short_name" text,
17 + "description" text,
18 + "level" integer DEFAULT 0 NOT NULL,
19 + "phase" integer DEFAULT 3 NOT NULL,
20 + "active" boolean DEFAULT true NOT NULL,
21 + "condition_scale" text,
22 + "graders" text[] DEFAULT '{}'::text[] NOT NULL,
23 + "index_ticker" text,
24 + "attribute_schema" jsonb DEFAULT '{}'::jsonb NOT NULL,
25 + "sort_order" integer DEFAULT 0 NOT NULL,
26 + "compliance_flags" text[] DEFAULT '{}'::text[] NOT NULL,
27 + "icon" text,
28 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
29 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
30 +);
31 +--> statement-breakpoint
32 +CREATE TABLE "franchises" (
33 + "slug" text PRIMARY KEY NOT NULL,
34 + "name" text NOT NULL,
35 + "owner" text,
36 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
37 + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
38 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
39 +);
40 +--> statement-breakpoint
41 +CREATE TABLE "graders" (
42 + "slug" text PRIMARY KEY NOT NULL,
43 + "name" text NOT NULL,
44 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
45 + "scale" jsonb DEFAULT '{}'::jsonb NOT NULL,
46 + "population_url" text,
47 + "verify_url" text,
48 + "active" boolean DEFAULT true NOT NULL
49 +);
50 +--> statement-breakpoint
51 +CREATE TABLE "sets" (
52 + "slug" text PRIMARY KEY NOT NULL,
53 + "name" text NOT NULL,
54 + "code" text,
55 + "category_slug" text NOT NULL,
56 + "franchise_slug" text,
57 + "brand_slug" text,
58 + "release_year" integer,
59 + "release_date" text,
60 + "language" text,
61 + "total_items" integer,
62 + "identifiers" jsonb DEFAULT '{}'::jsonb NOT NULL,
63 + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
64 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
65 +);
66 +--> statement-breakpoint
67 +CREATE TABLE "taxonomy_proposals" (
68 + "id" text PRIMARY KEY NOT NULL,
69 + "proposed_slug" text NOT NULL,
70 + "name" text NOT NULL,
71 + "parent_slug" text,
72 + "evidence" jsonb DEFAULT '{}'::jsonb NOT NULL,
73 + "volume_estimate" integer,
74 + "status" text DEFAULT 'pending' NOT NULL,
75 + "decided_by" text,
76 + "decided_at" text,
77 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
78 +);
79 +--> statement-breakpoint
80 +CREATE TABLE "connector_health" (
81 + "connector_id" text PRIMARY KEY NOT NULL,
82 + "computed_at" timestamp with time zone NOT NULL,
83 + "status" text NOT NULL,
84 + "health" jsonb DEFAULT '{}'::jsonb NOT NULL
85 +);
86 +--> statement-breakpoint
87 +CREATE TABLE "connector_runs" (
88 + "id" text PRIMARY KEY NOT NULL,
89 + "connector_id" text NOT NULL,
90 + "trigger" text DEFAULT 'schedule' NOT NULL,
91 + "started_at" timestamp with time zone NOT NULL,
92 + "finished_at" timestamp with time zone,
93 + "status" text DEFAULT 'running' NOT NULL,
94 + "pages_attempted" integer DEFAULT 0 NOT NULL,
95 + "pages_success" integer DEFAULT 0 NOT NULL,
96 + "records_raw" integer DEFAULT 0 NOT NULL,
97 + "records_normalized" integer DEFAULT 0 NOT NULL,
98 + "records_duplicate" integer DEFAULT 0 NOT NULL,
99 + "records_rejected" integer DEFAULT 0 NOT NULL,
100 + "engine_stats" jsonb DEFAULT '{}'::jsonb NOT NULL,
101 + "anomalies" jsonb DEFAULT '[]'::jsonb NOT NULL,
102 + "error" text,
103 + "cost_credits" real DEFAULT 0 NOT NULL,
104 + "cost_usd_est" real DEFAULT 0 NOT NULL,
105 + "cursor" jsonb DEFAULT '{}'::jsonb NOT NULL
106 +);
107 +--> statement-breakpoint
108 +CREATE TABLE "connectors" (
109 + "id" text PRIMARY KEY NOT NULL,
110 + "source_id" text NOT NULL,
111 + "display_name" text NOT NULL,
112 + "engine_priority" text[] DEFAULT '{}'::text[] NOT NULL,
113 + "categories" text[] DEFAULT '{}'::text[] NOT NULL,
114 + "regions" text[] DEFAULT '{}'::text[] NOT NULL,
115 + "languages" text[] DEFAULT '{}'::text[] NOT NULL,
116 + "currency" text[] DEFAULT '{}'::text[] NOT NULL,
117 + "supports_listings" boolean DEFAULT false NOT NULL,
118 + "supports_sold" boolean DEFAULT false NOT NULL,
119 + "supports_auctions" boolean DEFAULT false NOT NULL,
120 + "supports_images" boolean DEFAULT true NOT NULL,
121 + "supports_catalog" boolean DEFAULT false NOT NULL,
122 + "supports_population" boolean DEFAULT false NOT NULL,
123 + "refresh_frequency_minutes" integer DEFAULT 1440 NOT NULL,
124 + "priority" text DEFAULT 'medium' NOT NULL,
125 + "status" text DEFAULT 'active' NOT NULL,
126 + "schema_version" text DEFAULT '1.0' NOT NULL,
127 + "connector_version" text DEFAULT '1.0.0' NOT NULL,
128 + "config" jsonb DEFAULT '{}'::jsonb NOT NULL,
129 + "last_run_at" timestamp with time zone,
130 + "last_success_at" timestamp with time zone,
131 + "next_run_at" timestamp with time zone,
132 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
133 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
134 +);
135 +--> statement-breakpoint
136 +CREATE TABLE "costs" (
137 + "id" text PRIMARY KEY NOT NULL,
138 + "occurred_at" timestamp with time zone NOT NULL,
139 + "kind" text NOT NULL,
140 + "provider" text,
141 + "connector_id" text,
142 + "category_slug" text,
143 + "endpoint" text,
144 + "user_id" text,
145 + "units" real DEFAULT 1 NOT NULL,
146 + "credits" real DEFAULT 0 NOT NULL,
147 + "usd_est" real DEFAULT 0 NOT NULL,
148 + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL
149 +);
150 +--> statement-breakpoint
151 +CREATE TABLE "crawl_state" (
152 + "url_hash" text PRIMARY KEY NOT NULL,
153 + "connector_id" text NOT NULL,
154 + "url" text NOT NULL,
155 + "etag" text,
156 + "last_modified" text,
157 + "content_hash" text,
158 + "last_fetched_at" timestamp with time zone,
159 + "last_changed_at" timestamp with time zone,
160 + "fetch_count" integer DEFAULT 0 NOT NULL,
161 + "change_count" integer DEFAULT 0 NOT NULL,
162 + "change_interval_hours" real,
163 + "next_fetch_at" timestamp with time zone,
164 + "last_status" integer,
165 + "failures" integer DEFAULT 0 NOT NULL
166 +);
167 +--> statement-breakpoint
168 +CREATE TABLE "sources" (
169 + "id" text PRIMARY KEY NOT NULL,
170 + "name" text NOT NULL,
171 + "source_type" text NOT NULL,
172 + "url" text,
173 + "countries" text[] DEFAULT '{}'::text[] NOT NULL,
174 + "languages" text[] DEFAULT '{}'::text[] NOT NULL,
175 + "currencies" text[] DEFAULT '{}'::text[] NOT NULL,
176 + "trust_score" numeric(8, 6) DEFAULT 0.5 NOT NULL,
177 + "trust_factors" jsonb DEFAULT '{}'::jsonb NOT NULL,
178 + "attribution_required" boolean DEFAULT true NOT NULL,
179 + "terms_url" text,
180 + "active" boolean DEFAULT true NOT NULL,
181 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
182 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
183 +);
184 +--> statement-breakpoint
185 +CREATE TABLE "audit_log" (
186 + "id" text PRIMARY KEY NOT NULL,
187 + "entity_type" text NOT NULL,
188 + "entity_id" text NOT NULL,
189 + "action" text NOT NULL,
190 + "reason" text NOT NULL,
191 + "actor" text DEFAULT 'system' NOT NULL,
192 + "details" jsonb DEFAULT '{}'::jsonb NOT NULL,
193 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
194 +);
195 +--> statement-breakpoint
196 +CREATE TABLE "events" (
197 + "id" text PRIMARY KEY NOT NULL,
198 + "type" text NOT NULL,
199 + "entity_type" text,
200 + "entity_id" text,
201 + "payload" jsonb DEFAULT '{}'::jsonb NOT NULL,
202 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
203 +);
204 +--> statement-breakpoint
205 +CREATE TABLE "normalized_records" (
206 + "id" text PRIMARY KEY NOT NULL,
207 + "raw_record_id" text NOT NULL,
208 + "connector_id" text NOT NULL,
209 + "source_id" text NOT NULL,
210 + "kind" text NOT NULL,
211 + "payload" jsonb NOT NULL,
212 + "asset_id" text,
213 + "variant_id" text,
214 + "match_method" text,
215 + "match_confidence" numeric(8, 6),
216 + "status" text DEFAULT 'pending' NOT NULL,
217 + "reject_reason" text,
218 + "target_id" text,
219 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
220 + "processed_at" timestamp with time zone
221 +);
222 +--> statement-breakpoint
223 +CREATE TABLE "raw_records" (
224 + "id" text PRIMARY KEY NOT NULL,
225 + "connector_id" text NOT NULL,
226 + "source_id" text NOT NULL,
227 + "run_id" text,
228 + "engine" text NOT NULL,
229 + "url" text NOT NULL,
230 + "external_id" text,
231 + "kind" text NOT NULL,
232 + "fetched_at" timestamp with time zone NOT NULL,
233 + "content_hash" text NOT NULL,
234 + "http_status" integer,
235 + "payload" jsonb NOT NULL,
236 + "snapshot_ref" text,
237 + "parser_version" text NOT NULL,
238 + "connector_version" text NOT NULL,
239 + "processed_at" timestamp with time zone,
240 + "process_error" text,
241 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
242 +);
243 +--> statement-breakpoint
244 +CREATE TABLE "asset_embeddings" (
245 + "asset_id" text PRIMARY KEY NOT NULL,
246 + "model" text NOT NULL,
247 + "embedding" vector(1536) NOT NULL,
248 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
249 +);
250 +--> statement-breakpoint
251 +CREATE TABLE "asset_stats" (
252 + "asset_id" text PRIMARY KEY NOT NULL,
253 + "riv_usd" numeric(18, 4),
254 + "riv_low_usd" numeric(18, 4),
255 + "riv_high_usd" numeric(18, 4),
256 + "riv_confidence" numeric(8, 6),
257 + "riv_sample_size" integer DEFAULT 0 NOT NULL,
258 + "riv_variant_id" text,
259 + "latest_sale_usd" numeric(18, 4),
260 + "latest_sale_at" timestamp with time zone,
261 + "change_1d" numeric(8, 6),
262 + "change_7d" numeric(8, 6),
263 + "change_30d" numeric(8, 6),
264 + "change_90d" numeric(8, 6),
265 + "change_1y" numeric(8, 6),
266 + "ath_usd" numeric(18, 4),
267 + "ath_at" timestamp with time zone,
268 + "atl_usd" numeric(18, 4),
269 + "atl_at" timestamp with time zone,
270 + "sales_count" integer DEFAULT 0 NOT NULL,
271 + "sales_30d" integer DEFAULT 0 NOT NULL,
272 + "sales_1y" integer DEFAULT 0 NOT NULL,
273 + "volume_30d_usd" numeric(18, 4),
274 + "active_listings" integer DEFAULT 0 NOT NULL,
275 + "min_ask_usd" numeric(18, 4),
276 + "observations_count" integer DEFAULT 0 NOT NULL,
277 + "sources_count" integer DEFAULT 0 NOT NULL,
278 + "liquidity_score" real,
279 + "rarity_score" real,
280 + "momentum_7d" real,
281 + "momentum_30d" real,
282 + "momentum_90d" real,
283 + "momentum_1y" real,
284 + "trending_score" real,
285 + "value_opportunity" real,
286 + "data_quality" real,
287 + "watchers" integer DEFAULT 0 NOT NULL,
288 + "views_30d" integer DEFAULT 0 NOT NULL,
289 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
290 +);
291 +--> statement-breakpoint
292 +CREATE TABLE "asset_variants" (
293 + "id" text PRIMARY KEY NOT NULL,
294 + "asset_id" text NOT NULL,
295 + "variant_key" text NOT NULL,
296 + "grader" text,
297 + "grade" text,
298 + "qualifier" text,
299 + "condition" text,
300 + "completeness" text,
301 + "size_label" text,
302 + "label" text NOT NULL,
303 + "is_default" boolean DEFAULT false NOT NULL,
304 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
305 +);
306 +--> statement-breakpoint
307 +CREATE TABLE "assets" (
308 + "id" text PRIMARY KEY NOT NULL,
309 + "slug" text NOT NULL,
310 + "canonical_key" text NOT NULL,
311 + "category_slug" text NOT NULL,
312 + "subcategory_slug" text,
313 + "family_slug" text NOT NULL,
314 + "franchise" text,
315 + "brand" text,
316 + "series" text,
317 + "set_slug" text,
318 + "set_name" text,
319 + "set_code" text,
320 + "name" text NOT NULL,
321 + "title" text NOT NULL,
322 + "model" text,
323 + "reference" text,
324 + "number" text,
325 + "year" integer,
326 + "edition" text,
327 + "variant" text,
328 + "language" text,
329 + "region" text,
330 + "country" text,
331 + "material" text,
332 + "size" text,
333 + "color" text,
334 + "rarity" text,
335 + "production_quantity" integer,
336 + "original_msrp" numeric(18, 4),
337 + "original_msrp_currency" text,
338 + "release_date" text,
339 + "description" text,
340 + "hero_image_url" text,
341 + "identifiers" jsonb DEFAULT '{}'::jsonb NOT NULL,
342 + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
343 + "merged_from" text[] DEFAULT '{}'::text[] NOT NULL,
344 + "data_quality" real DEFAULT 0 NOT NULL,
345 + "verified" boolean DEFAULT false NOT NULL,
346 + "search" "tsvector" GENERATED ALWAYS AS (setweight(to_tsvector('simple', coalesce(name, '')), 'A') || setweight(to_tsvector('simple', coalesce(set_name, '') || ' ' || coalesce(number, '') || ' ' || coalesce(variant, '') || ' ' || coalesce(edition, '')), 'B') || setweight(to_tsvector('simple', coalesce(brand, '') || ' ' || coalesce(franchise, '') || ' ' || coalesce(reference, '') || ' ' || coalesce(model, '') || ' ' || coalesce(year::text, '') || ' ' || coalesce(category_slug, '')), 'C')) STORED,
347 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
348 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
349 +);
350 +--> statement-breakpoint
351 +CREATE TABLE "grade_premiums" (
352 + "id" text PRIMARY KEY NOT NULL,
353 + "category_slug" text NOT NULL,
354 + "grader" text NOT NULL,
355 + "grade" text NOT NULL,
356 + "market_multiplier" real NOT NULL,
357 + "sample_size" integer NOT NULL,
358 + "computed_at" timestamp with time zone NOT NULL
359 +);
360 +--> statement-breakpoint
361 +CREATE TABLE "images" (
362 + "id" text PRIMARY KEY NOT NULL,
363 + "asset_id" text,
364 + "listing_id" text,
365 + "sale_id" text,
366 + "source_id" text,
367 + "url" text NOT NULL,
368 + "role" text DEFAULT 'gallery' NOT NULL,
369 + "width" integer,
370 + "height" integer,
371 + "phash" text,
372 + "embedding" vector(512),
373 + "attribution" text,
374 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
375 +);
376 +--> statement-breakpoint
377 +CREATE TABLE "population_reports" (
378 + "id" text PRIMARY KEY NOT NULL,
379 + "asset_id" text NOT NULL,
380 + "grader" text NOT NULL,
381 + "source_id" text NOT NULL,
382 + "source_url" text,
383 + "report_date" text NOT NULL,
384 + "total" integer NOT NULL,
385 + "by_grade" jsonb NOT NULL,
386 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
387 +);
388 +--> statement-breakpoint
389 +CREATE TABLE "variant_stats" (
390 + "variant_id" text PRIMARY KEY NOT NULL,
391 + "asset_id" text NOT NULL,
392 + "riv_usd" numeric(18, 4),
393 + "riv_low_usd" numeric(18, 4),
394 + "riv_high_usd" numeric(18, 4),
395 + "riv_confidence" numeric(8, 6),
396 + "riv_sample_size" integer DEFAULT 0 NOT NULL,
397 + "latest_sale_usd" numeric(18, 4),
398 + "latest_sale_at" timestamp with time zone,
399 + "change_30d" numeric(8, 6),
400 + "change_1y" numeric(8, 6),
401 + "sales_count" integer DEFAULT 0 NOT NULL,
402 + "sales_30d" integer DEFAULT 0 NOT NULL,
403 + "active_listings" integer DEFAULT 0 NOT NULL,
404 + "min_ask_usd" numeric(18, 4),
405 + "liquidity_score" real,
406 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
407 +);
408 +--> statement-breakpoint
409 +CREATE TABLE "auction_lots" (
410 + "id" text PRIMARY KEY NOT NULL,
411 + "auction_id" text NOT NULL,
412 + "asset_id" text,
413 + "variant_id" text,
414 + "source_id" text NOT NULL,
415 + "lot_number" text,
416 + "title" text NOT NULL,
417 + "url" text NOT NULL,
418 + "estimate_low" numeric(18, 4),
419 + "estimate_high" numeric(18, 4),
420 + "current_bid" numeric(18, 4),
421 + "hammer_price" numeric(18, 4),
422 + "currency" text,
423 + "bid_count" integer,
424 + "starts_at" timestamp with time zone,
425 + "ends_at" timestamp with time zone,
426 + "status" text DEFAULT 'upcoming' NOT NULL,
427 + "image_urls" jsonb DEFAULT '[]'::jsonb NOT NULL,
428 + "grader" text,
429 + "grade" text,
430 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
431 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
432 +);
433 +--> statement-breakpoint
434 +CREATE TABLE "auctions" (
435 + "id" text PRIMARY KEY NOT NULL,
436 + "source_id" text NOT NULL,
437 + "auction_house" text NOT NULL,
438 + "name" text NOT NULL,
439 + "url" text NOT NULL,
440 + "starts_at" timestamp with time zone,
441 + "ends_at" timestamp with time zone,
442 + "location" text,
443 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
444 + "lot_count" integer,
445 + "status" text DEFAULT 'upcoming' NOT NULL,
446 + "currency" text,
447 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
448 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
449 +);
450 +--> statement-breakpoint
451 +CREATE TABLE "cross_listing_groups" (
452 + "id" text PRIMARY KEY NOT NULL,
453 + "asset_id" text,
454 + "signals" jsonb DEFAULT '{}'::jsonb NOT NULL,
455 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
456 +);
457 +--> statement-breakpoint
458 +CREATE TABLE "fx_rates" (
459 + "date" date NOT NULL,
460 + "base" text NOT NULL,
461 + "quote" text NOT NULL,
462 + "rate" real NOT NULL,
463 + "source" text DEFAULT 'ecb' NOT NULL
464 +);
465 +--> statement-breakpoint
466 +CREATE TABLE "listing_events" (
467 + "id" text PRIMARY KEY NOT NULL,
468 + "listing_id" text NOT NULL,
469 + "event_type" text NOT NULL,
470 + "old_price" numeric(18, 4),
471 + "new_price" numeric(18, 4),
472 + "currency" text,
473 + "occurred_at" timestamp with time zone NOT NULL
474 +);
475 +--> statement-breakpoint
476 +CREATE TABLE "listings" (
477 + "id" text PRIMARY KEY NOT NULL,
478 + "asset_id" text NOT NULL,
479 + "variant_id" text,
480 + "source_id" text NOT NULL,
481 + "connector_id" text NOT NULL,
482 + "raw_record_id" text,
483 + "source_url" text NOT NULL,
484 + "external_id" text NOT NULL,
485 + "listing_type" text DEFAULT 'unknown' NOT NULL,
486 + "price" numeric(18, 4),
487 + "currency" text,
488 + "price_usd" numeric(18, 4),
489 + "seller" text,
490 + "seller_reputation" text,
491 + "location" text,
492 + "shipping_cost" numeric(18, 4),
493 + "quantity" integer,
494 + "condition" text,
495 + "grader" text,
496 + "grade" text,
497 + "certification_number" text,
498 + "image_urls" jsonb DEFAULT '[]'::jsonb NOT NULL,
499 + "raw_title" text NOT NULL,
500 + "description" text,
501 + "listed_at" timestamp with time zone,
502 + "ends_at" timestamp with time zone,
503 + "availability" text DEFAULT 'available' NOT NULL,
504 + "bid_count" integer,
505 + "first_seen_at" timestamp with time zone NOT NULL,
506 + "last_seen_at" timestamp with time zone NOT NULL,
507 + "price_changed_at" timestamp with time zone,
508 + "cross_listing_group_id" text,
509 + "confidence" numeric(8, 6) DEFAULT 0.8 NOT NULL,
510 + "data_quality" real DEFAULT 0 NOT NULL,
511 + "flags" text[] DEFAULT '{}'::text[] NOT NULL,
512 + "discount_to_riv" numeric(8, 6),
513 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
514 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
515 +);
516 +--> statement-breakpoint
517 +CREATE TABLE "news" (
518 + "id" text PRIMARY KEY NOT NULL,
519 + "source_id" text NOT NULL,
520 + "url" text NOT NULL,
521 + "title" text NOT NULL,
522 + "summary" text,
523 + "ai_summary" text,
524 + "published_at" timestamp with time zone,
525 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
526 + "news_type" text,
527 + "image_url" text,
528 + "fetched_at" timestamp with time zone NOT NULL
529 +);
530 +--> statement-breakpoint
531 +CREATE TABLE "price_observations" (
532 + "id" text PRIMARY KEY NOT NULL,
533 + "asset_id" text NOT NULL,
534 + "variant_id" text,
535 + "source_id" text NOT NULL,
536 + "connector_id" text NOT NULL,
537 + "raw_record_id" text,
538 + "source_url" text NOT NULL,
539 + "price_kind" text NOT NULL,
540 + "price" numeric(18, 4) NOT NULL,
541 + "currency" text NOT NULL,
542 + "price_usd" numeric(18, 4) NOT NULL,
543 + "observation_date" date NOT NULL,
544 + "sample_size" integer,
545 + "dedupe_key" text NOT NULL,
546 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
547 +);
548 +--> statement-breakpoint
549 +CREATE TABLE "sales" (
550 + "id" text PRIMARY KEY NOT NULL,
551 + "asset_id" text NOT NULL,
552 + "variant_id" text,
553 + "source_id" text NOT NULL,
554 + "connector_id" text NOT NULL,
555 + "raw_record_id" text,
556 + "normalized_record_id" text,
557 + "source_url" text NOT NULL,
558 + "external_id" text,
559 + "sale_type" text DEFAULT 'unknown' NOT NULL,
560 + "sale_date" timestamp with time zone NOT NULL,
561 + "price" numeric(18, 4) NOT NULL,
562 + "currency" text NOT NULL,
563 + "price_usd" numeric(18, 4) NOT NULL,
564 + "fx_rate" real,
565 + "fx_date" date,
566 + "buyer_premium_included" boolean,
567 + "quantity" integer DEFAULT 1 NOT NULL,
568 + "is_bundle" boolean DEFAULT false NOT NULL,
569 + "condition" text,
570 + "grader" text,
571 + "grade" text,
572 + "certification_number" text,
573 + "location" text,
574 + "auction_house" text,
575 + "lot_number" text,
576 + "image_urls" jsonb DEFAULT '[]'::jsonb NOT NULL,
577 + "raw_title" text NOT NULL,
578 + "confidence" numeric(8, 6) DEFAULT 0.8 NOT NULL,
579 + "data_quality" real DEFAULT 0 NOT NULL,
580 + "status" text DEFAULT 'valid' NOT NULL,
581 + "flags" text[] DEFAULT '{}'::text[] NOT NULL,
582 + "dedupe_key" text NOT NULL,
583 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
584 +);
585 +--> statement-breakpoint
586 +CREATE TABLE "benchmarks" (
587 + "ticker" text NOT NULL,
588 + "date" date NOT NULL,
589 + "value" real NOT NULL,
590 + "source" text NOT NULL,
591 + CONSTRAINT "benchmarks_ticker_date_pk" PRIMARY KEY("ticker","date")
592 +);
593 +--> statement-breakpoint
594 +CREATE TABLE "category_snapshots" (
595 + "category_slug" text NOT NULL,
596 + "date" date NOT NULL,
597 + "index_value" real,
598 + "tracked_assets" integer DEFAULT 0 NOT NULL,
599 + "assets_with_valuation" integer DEFAULT 0 NOT NULL,
600 + "sales" integer DEFAULT 0 NOT NULL,
601 + "volume_usd" numeric(18, 4),
602 + "median_sale_usd" numeric(18, 4),
603 + "active_listings" integer DEFAULT 0 NOT NULL,
604 + "market_cap_est_usd" numeric(18, 4),
605 + "liquidity_score" real,
606 + "change_1d" numeric(8, 6),
607 + "change_7d" numeric(8, 6),
608 + "change_30d" numeric(8, 6),
609 + "change_1y" numeric(8, 6),
610 + CONSTRAINT "category_snapshots_category_slug_date_pk" PRIMARY KEY("category_slug","date")
611 +);
612 +--> statement-breakpoint
613 +CREATE TABLE "correlations" (
614 + "a" text NOT NULL,
615 + "b" text NOT NULL,
616 + "window_days" integer NOT NULL,
617 + "coefficient" real NOT NULL,
618 + "observations" integer NOT NULL,
619 + "computed_at" timestamp with time zone NOT NULL,
620 + CONSTRAINT "correlations_a_b_window_days_pk" PRIMARY KEY("a","b","window_days")
621 +);
622 +--> statement-breakpoint
623 +CREATE TABLE "index_constituents" (
624 + "index_id" text NOT NULL,
625 + "asset_id" text NOT NULL,
626 + "variant_id" text DEFAULT '' NOT NULL,
627 + "weight" real DEFAULT 1 NOT NULL,
628 + "added_at" date NOT NULL,
629 + "removed_at" date,
630 + "reason" text,
631 + CONSTRAINT "index_constituents_index_id_asset_id_variant_id_added_at_pk" PRIMARY KEY("index_id","asset_id","variant_id","added_at")
632 +);
633 +--> statement-breakpoint
634 +CREATE TABLE "index_values" (
635 + "index_id" text NOT NULL,
636 + "date" date NOT NULL,
637 + "value" real NOT NULL,
638 + "constituents_count" integer DEFAULT 0 NOT NULL,
639 + "transactions" integer DEFAULT 0 NOT NULL,
640 + "volume_usd" numeric(18, 4),
641 + "median_sale_usd" numeric(18, 4),
642 + "avg_sale_usd" numeric(18, 4),
643 + "market_cap_est_usd" numeric(18, 4),
644 + "market_cap_confidence" text,
645 + "liquidity_score" real,
646 + "momentum" real,
647 + "breadth" integer,
648 + "tracked_assets" integer,
649 + "coverage" real,
650 + CONSTRAINT "index_values_index_id_date_pk" PRIMARY KEY("index_id","date")
651 +);
652 +--> statement-breakpoint
653 +CREATE TABLE "indices" (
654 + "id" text PRIMARY KEY NOT NULL,
655 + "ticker" text NOT NULL,
656 + "name" text NOT NULL,
657 + "description" text,
658 + "category_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
659 + "family_slugs" text[] DEFAULT '{}'::text[] NOT NULL,
660 + "parent_ticker" text,
661 + "methodology" text DEFAULT 'chain_linked_equal_weight_v1' NOT NULL,
662 + "weighting" text DEFAULT 'equal' NOT NULL,
663 + "base_date" date NOT NULL,
664 + "base_value" real DEFAULT 1000 NOT NULL,
665 + "min_constituents" integer DEFAULT 10 NOT NULL,
666 + "active" boolean DEFAULT true NOT NULL,
667 + "is_flagship" boolean DEFAULT false NOT NULL,
668 + "color" text,
669 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
670 + CONSTRAINT "indices_ticker_unique" UNIQUE("ticker")
671 +);
672 +--> statement-breakpoint
673 +CREATE TABLE "price_snapshots" (
674 + "asset_id" text NOT NULL,
675 + "variant_id" text DEFAULT '' NOT NULL,
676 + "date" date NOT NULL,
677 + "riv_usd" numeric(18, 4),
678 + "latest_sale_usd" numeric(18, 4),
679 + "median_usd" numeric(18, 4),
680 + "sales_count" integer DEFAULT 0 NOT NULL,
681 + "volume_usd" numeric(18, 4),
682 + "listings_count" integer DEFAULT 0 NOT NULL,
683 + "min_ask_usd" numeric(18, 4),
684 + "observation_usd" numeric(18, 4),
685 + CONSTRAINT "price_snapshots_asset_id_variant_id_date_pk" PRIMARY KEY("asset_id","variant_id","date")
686 +);
687 +--> statement-breakpoint
688 +CREATE TABLE "radar_findings" (
689 + "id" text PRIMARY KEY NOT NULL,
690 + "asset_id" text NOT NULL,
691 + "kind" text NOT NULL,
692 + "score" real NOT NULL,
693 + "evidence" jsonb DEFAULT '{}'::jsonb NOT NULL,
694 + "entity_type" text,
695 + "entity_id" text,
696 + "detected_at" timestamp with time zone NOT NULL,
697 + "expires_at" timestamp with time zone
698 +);
699 +--> statement-breakpoint
700 +CREATE TABLE "valuations" (
701 + "id" text PRIMARY KEY NOT NULL,
702 + "asset_id" text NOT NULL,
703 + "variant_id" text,
704 + "computed_at" timestamp with time zone NOT NULL,
705 + "riv_usd" numeric(18, 4),
706 + "low_usd" numeric(18, 4),
707 + "high_usd" numeric(18, 4),
708 + "confidence" numeric(8, 6) DEFAULT 0 NOT NULL,
709 + "confidence_label" text DEFAULT 'insufficient' NOT NULL,
710 + "sample_size" integer DEFAULT 0 NOT NULL,
711 + "window_days" integer DEFAULT 365 NOT NULL,
712 + "methods" jsonb DEFAULT '{}'::jsonb NOT NULL,
713 + "sales_used" text[] DEFAULT '{}'::text[] NOT NULL,
714 + "observations_used" integer DEFAULT 0 NOT NULL,
715 + "method" text DEFAULT 'ensemble_v1' NOT NULL,
716 + "notes" text[] DEFAULT '{}'::text[] NOT NULL
717 +);
718 +--> statement-breakpoint
719 +CREATE TABLE "alert_events" (
720 + "id" text PRIMARY KEY NOT NULL,
721 + "alert_id" text NOT NULL,
722 + "user_id" text NOT NULL,
723 + "message" text NOT NULL,
724 + "payload" jsonb DEFAULT '{}'::jsonb NOT NULL,
725 + "read_at" timestamp with time zone,
726 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
727 +);
728 +--> statement-breakpoint
729 +CREATE TABLE "alerts" (
730 + "id" text PRIMARY KEY NOT NULL,
731 + "user_id" text NOT NULL,
732 + "alert_type" text NOT NULL,
733 + "target_type" text NOT NULL,
734 + "target_id" text NOT NULL,
735 + "threshold" numeric(18, 4),
736 + "currency" text,
737 + "params" jsonb DEFAULT '{}'::jsonb NOT NULL,
738 + "channel" text DEFAULT 'inapp' NOT NULL,
739 + "active" boolean DEFAULT true NOT NULL,
740 + "last_triggered_at" timestamp with time zone,
741 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
742 +);
743 +--> statement-breakpoint
744 +CREATE TABLE "api_keys" (
745 + "id" text PRIMARY KEY NOT NULL,
746 + "user_id" text,
747 + "name" text NOT NULL,
748 + "prefix" text NOT NULL,
749 + "key_hash" text NOT NULL,
750 + "tier" text DEFAULT 'free' NOT NULL,
751 + "rate_limit_per_minute" integer DEFAULT 60 NOT NULL,
752 + "daily_quota" integer DEFAULT 1000 NOT NULL,
753 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
754 + "last_used_at" timestamp with time zone,
755 + "revoked_at" timestamp with time zone,
756 + CONSTRAINT "api_keys_key_hash_unique" UNIQUE("key_hash")
757 +);
758 +--> statement-breakpoint
759 +CREATE TABLE "api_usage" (
760 + "key_id" text NOT NULL,
761 + "date" date NOT NULL,
762 + "endpoint" text NOT NULL,
763 + "count" integer DEFAULT 0 NOT NULL,
764 + "latency_ms_avg" real,
765 + CONSTRAINT "api_usage_key_id_date_endpoint_pk" PRIMARY KEY("key_id","date","endpoint")
766 +);
767 +--> statement-breakpoint
768 +CREATE TABLE "asset_views" (
769 + "asset_id" text NOT NULL,
770 + "date" date NOT NULL,
771 + "views" integer DEFAULT 0 NOT NULL,
772 + CONSTRAINT "asset_views_asset_id_date_pk" PRIMARY KEY("asset_id","date")
773 +);
774 +--> statement-breakpoint
775 +CREATE TABLE "collection_items" (
776 + "id" text PRIMARY KEY NOT NULL,
777 + "collection_id" text NOT NULL,
778 + "asset_id" text NOT NULL,
779 + "variant_id" text,
780 + "quantity" integer DEFAULT 1 NOT NULL,
781 + "acquired_at" date,
782 + "purchase_price" numeric(18, 4),
783 + "purchase_currency" text,
784 + "purchase_price_usd" numeric(18, 4),
785 + "source" text,
786 + "grader" text,
787 + "grade" text,
788 + "certification_number" text,
789 + "serial" text,
790 + "photos" jsonb DEFAULT '[]'::jsonb NOT NULL,
791 + "notes" text,
792 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
793 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
794 +);
795 +--> statement-breakpoint
796 +CREATE TABLE "collection_snapshots" (
797 + "collection_id" text NOT NULL,
798 + "date" date NOT NULL,
799 + "value_usd" numeric(18, 4) NOT NULL,
800 + "cost_basis_usd" numeric(18, 4) NOT NULL,
801 + "items" integer NOT NULL,
802 + CONSTRAINT "collection_snapshots_collection_id_date_pk" PRIMARY KEY("collection_id","date")
803 +);
804 +--> statement-breakpoint
805 +CREATE TABLE "collections" (
806 + "id" text PRIMARY KEY NOT NULL,
807 + "user_id" text NOT NULL,
808 + "name" text NOT NULL,
809 + "description" text,
810 + "is_public" boolean DEFAULT false NOT NULL,
811 + "public_slug" text,
812 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
813 + "updated_at" timestamp with time zone DEFAULT now() NOT NULL
814 +);
815 +--> statement-breakpoint
816 +CREATE TABLE "search_log" (
817 + "id" text PRIMARY KEY NOT NULL,
818 + "query" text NOT NULL,
819 + "normalized" text NOT NULL,
820 + "results" integer NOT NULL,
821 + "user_id" text,
822 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
823 +);
824 +--> statement-breakpoint
825 +CREATE TABLE "sessions" (
826 + "id" text PRIMARY KEY NOT NULL,
827 + "user_id" text NOT NULL,
828 + "expires_at" timestamp with time zone NOT NULL,
829 + "user_agent" text,
830 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
831 +);
832 +--> statement-breakpoint
833 +CREATE TABLE "users" (
834 + "id" text PRIMARY KEY NOT NULL,
835 + "email" text NOT NULL,
836 + "email_verified_at" timestamp with time zone,
837 + "password_hash" text,
838 + "name" text,
839 + "role" text DEFAULT 'user' NOT NULL,
840 + "display_currency" text DEFAULT 'USD' NOT NULL,
841 + "providers" jsonb DEFAULT '[]'::jsonb NOT NULL,
842 + "preferences" jsonb DEFAULT '{}'::jsonb NOT NULL,
843 + "created_at" timestamp with time zone DEFAULT now() NOT NULL,
844 + "last_login_at" timestamp with time zone,
845 + CONSTRAINT "users_email_unique" UNIQUE("email")
846 +);
847 +--> statement-breakpoint
848 +CREATE TABLE "watchlist_items" (
849 + "id" text PRIMARY KEY NOT NULL,
850 + "watchlist_id" text NOT NULL,
851 + "target_type" text NOT NULL,
852 + "target_id" text NOT NULL,
853 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
854 +);
855 +--> statement-breakpoint
856 +CREATE TABLE "watchlists" (
857 + "id" text PRIMARY KEY NOT NULL,
858 + "user_id" text NOT NULL,
859 + "name" text DEFAULT 'Watchlist' NOT NULL,
860 + "created_at" timestamp with time zone DEFAULT now() NOT NULL
861 +);
862 +--> statement-breakpoint
863 +CREATE INDEX "brands_name_idx" ON "brands" USING btree ("name");--> statement-breakpoint
864 +CREATE INDEX "categories_parent_idx" ON "categories" USING btree ("parent_slug");--> statement-breakpoint
865 +CREATE INDEX "categories_family_idx" ON "categories" USING btree ("family_slug");--> statement-breakpoint
866 +CREATE INDEX "sets_category_idx" ON "sets" USING btree ("category_slug");--> statement-breakpoint
867 +CREATE UNIQUE INDEX "sets_category_code_uq" ON "sets" USING btree ("category_slug","code");--> statement-breakpoint
868 +CREATE INDEX "connector_runs_connector_started_idx" ON "connector_runs" USING btree ("connector_id","started_at");--> statement-breakpoint
869 +CREATE INDEX "costs_occurred_idx" ON "costs" USING btree ("occurred_at");--> statement-breakpoint
870 +CREATE INDEX "costs_connector_idx" ON "costs" USING btree ("connector_id");--> statement-breakpoint
871 +CREATE INDEX "crawl_state_next_idx" ON "crawl_state" USING btree ("connector_id","next_fetch_at");--> statement-breakpoint
872 +CREATE INDEX "audit_entity_idx" ON "audit_log" USING btree ("entity_type","entity_id");--> statement-breakpoint
873 +CREATE INDEX "events_type_created_idx" ON "events" USING btree ("type","created_at");--> statement-breakpoint
874 +CREATE INDEX "events_entity_idx" ON "events" USING btree ("entity_type","entity_id");--> statement-breakpoint
875 +CREATE INDEX "normalized_records_status_idx" ON "normalized_records" USING btree ("status","created_at");--> statement-breakpoint
876 +CREATE INDEX "normalized_records_asset_idx" ON "normalized_records" USING btree ("asset_id");--> statement-breakpoint
877 +CREATE UNIQUE INDEX "normalized_records_raw_uq" ON "normalized_records" USING btree ("raw_record_id");--> statement-breakpoint
878 +CREATE UNIQUE INDEX "raw_records_connector_hash_uq" ON "raw_records" USING btree ("connector_id","content_hash");--> statement-breakpoint
879 +CREATE INDEX "raw_records_connector_fetched_idx" ON "raw_records" USING btree ("connector_id","fetched_at");--> statement-breakpoint
880 +CREATE INDEX "raw_records_unprocessed_idx" ON "raw_records" USING btree ("processed_at");--> statement-breakpoint
881 +CREATE INDEX "raw_records_external_idx" ON "raw_records" USING btree ("connector_id","external_id");--> statement-breakpoint
882 +CREATE INDEX "asset_stats_riv_idx" ON "asset_stats" USING btree ("riv_usd");--> statement-breakpoint
883 +CREATE INDEX "asset_stats_trending_idx" ON "asset_stats" USING btree ("trending_score");--> statement-breakpoint
884 +CREATE INDEX "asset_stats_liquidity_idx" ON "asset_stats" USING btree ("liquidity_score");--> statement-breakpoint
885 +CREATE UNIQUE INDEX "asset_variants_uq" ON "asset_variants" USING btree ("asset_id","variant_key");--> statement-breakpoint
886 +CREATE INDEX "asset_variants_asset_idx" ON "asset_variants" USING btree ("asset_id");--> statement-breakpoint
887 +CREATE UNIQUE INDEX "assets_canonical_key_uq" ON "assets" USING btree ("canonical_key");--> statement-breakpoint
888 +CREATE UNIQUE INDEX "assets_slug_uq" ON "assets" USING btree ("slug");--> statement-breakpoint
889 +CREATE INDEX "assets_category_idx" ON "assets" USING btree ("category_slug");--> statement-breakpoint
890 +CREATE INDEX "assets_family_idx" ON "assets" USING btree ("family_slug");--> statement-breakpoint
891 +CREATE INDEX "assets_set_idx" ON "assets" USING btree ("set_slug");--> statement-breakpoint
892 +CREATE INDEX "assets_search_gin" ON "assets" USING gin ("search");--> statement-breakpoint
893 +CREATE INDEX "assets_title_trgm" ON "assets" USING gin ("title" gin_trgm_ops);--> statement-breakpoint
894 +CREATE INDEX "assets_identifiers_gin" ON "assets" USING gin ("identifiers");--> statement-breakpoint
895 +CREATE UNIQUE INDEX "grade_premiums_uq" ON "grade_premiums" USING btree ("category_slug","grader","grade");--> statement-breakpoint
896 +CREATE INDEX "images_asset_idx" ON "images" USING btree ("asset_id");--> statement-breakpoint
897 +CREATE INDEX "images_phash_idx" ON "images" USING btree ("phash");--> statement-breakpoint
898 +CREATE UNIQUE INDEX "images_url_uq" ON "images" USING btree ("url");--> statement-breakpoint
899 +CREATE UNIQUE INDEX "population_reports_uq" ON "population_reports" USING btree ("asset_id","grader","report_date");--> statement-breakpoint
900 +CREATE UNIQUE INDEX "auction_lots_url_uq" ON "auction_lots" USING btree ("url");--> statement-breakpoint
901 +CREATE INDEX "auction_lots_auction_idx" ON "auction_lots" USING btree ("auction_id");--> statement-breakpoint
902 +CREATE INDEX "auction_lots_asset_idx" ON "auction_lots" USING btree ("asset_id");--> statement-breakpoint
903 +CREATE INDEX "auction_lots_ends_idx" ON "auction_lots" USING btree ("ends_at");--> statement-breakpoint
904 +CREATE UNIQUE INDEX "auctions_url_uq" ON "auctions" USING btree ("url");--> statement-breakpoint
905 +CREATE INDEX "auctions_ends_idx" ON "auctions" USING btree ("ends_at");--> statement-breakpoint
906 +CREATE UNIQUE INDEX "fx_rates_uq" ON "fx_rates" USING btree ("date","base","quote");--> statement-breakpoint
907 +CREATE INDEX "listing_events_listing_idx" ON "listing_events" USING btree ("listing_id","occurred_at");--> statement-breakpoint
908 +CREATE UNIQUE INDEX "listings_source_external_uq" ON "listings" USING btree ("source_id","external_id");--> statement-breakpoint
909 +CREATE INDEX "listings_asset_avail_idx" ON "listings" USING btree ("asset_id","availability");--> statement-breakpoint
910 +CREATE INDEX "listings_avail_price_idx" ON "listings" USING btree ("availability","price_usd");--> statement-breakpoint
911 +CREATE INDEX "listings_ends_idx" ON "listings" USING btree ("ends_at");--> statement-breakpoint
912 +CREATE INDEX "listings_last_seen_idx" ON "listings" USING btree ("last_seen_at");--> statement-breakpoint
913 +CREATE UNIQUE INDEX "news_url_uq" ON "news" USING btree ("url");--> statement-breakpoint
914 +CREATE INDEX "news_published_idx" ON "news" USING btree ("published_at");--> statement-breakpoint
915 +CREATE UNIQUE INDEX "price_observations_dedupe_uq" ON "price_observations" USING btree ("dedupe_key");--> statement-breakpoint
916 +CREATE INDEX "price_observations_asset_date_idx" ON "price_observations" USING btree ("asset_id","observation_date");--> statement-breakpoint
917 +CREATE UNIQUE INDEX "sales_dedupe_uq" ON "sales" USING btree ("dedupe_key");--> statement-breakpoint
918 +CREATE INDEX "sales_asset_date_idx" ON "sales" USING btree ("asset_id","sale_date");--> statement-breakpoint
919 +CREATE INDEX "sales_variant_date_idx" ON "sales" USING btree ("variant_id","sale_date");--> statement-breakpoint
920 +CREATE INDEX "sales_source_idx" ON "sales" USING btree ("source_id","sale_date");--> statement-breakpoint
921 +CREATE INDEX "sales_date_idx" ON "sales" USING btree ("sale_date");--> statement-breakpoint
922 +CREATE INDEX "sales_price_idx" ON "sales" USING btree ("price_usd");--> statement-breakpoint
923 +CREATE INDEX "index_constituents_asset_idx" ON "index_constituents" USING btree ("asset_id");--> statement-breakpoint
924 +CREATE INDEX "price_snapshots_date_idx" ON "price_snapshots" USING btree ("date");--> statement-breakpoint
925 +CREATE INDEX "radar_kind_idx" ON "radar_findings" USING btree ("kind","detected_at");--> statement-breakpoint
926 +CREATE UNIQUE INDEX "radar_entity_uq" ON "radar_findings" USING btree ("kind","entity_type","entity_id");--> statement-breakpoint
927 +CREATE INDEX "valuations_asset_idx" ON "valuations" USING btree ("asset_id","computed_at");--> statement-breakpoint
928 +CREATE INDEX "valuations_variant_idx" ON "valuations" USING btree ("variant_id","computed_at");--> statement-breakpoint
929 +CREATE INDEX "alert_events_user_idx" ON "alert_events" USING btree ("user_id","created_at");--> statement-breakpoint
930 +CREATE INDEX "alerts_user_idx" ON "alerts" USING btree ("user_id");--> statement-breakpoint
931 +CREATE INDEX "alerts_target_idx" ON "alerts" USING btree ("target_type","target_id","active");--> statement-breakpoint
932 +CREATE INDEX "api_keys_user_idx" ON "api_keys" USING btree ("user_id");--> statement-breakpoint
933 +CREATE INDEX "collection_items_collection_idx" ON "collection_items" USING btree ("collection_id");--> statement-breakpoint
934 +CREATE INDEX "collection_items_asset_idx" ON "collection_items" USING btree ("asset_id");--> statement-breakpoint
935 +CREATE INDEX "collections_user_idx" ON "collections" USING btree ("user_id");--> statement-breakpoint
936 +CREATE UNIQUE INDEX "collections_public_slug_uq" ON "collections" USING btree ("public_slug");--> statement-breakpoint
937 +CREATE INDEX "search_log_created_idx" ON "search_log" USING btree ("created_at");--> statement-breakpoint
938 +CREATE INDEX "search_log_normalized_idx" ON "search_log" USING btree ("normalized");--> statement-breakpoint
939 +CREATE INDEX "sessions_user_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint
940 +CREATE UNIQUE INDEX "watchlist_items_uq" ON "watchlist_items" USING btree ("watchlist_id","target_type","target_id");--> statement-breakpoint
941 +CREATE INDEX "watchlists_user_idx" ON "watchlists" USING btree ("user_id");
\ No newline at end of file
added packages/database/migrations/meta/0000_snapshot.json +6579 −0
@@ -0,0 +1,6579 @@
1 +{
2 + "id": "5a9ca0fc-7e8f-4e45-90e0-6c16a3a6a052",
3 + "prevId": "00000000-0000-0000-0000-000000000000",
4 + "version": "7",
5 + "dialect": "postgresql",
6 + "tables": {
7 + "public.brands": {
8 + "name": "brands",
9 + "schema": "",
10 + "columns": {
11 + "slug": {
12 + "name": "slug",
13 + "type": "text",
14 + "primaryKey": true,
15 + "notNull": true
16 + },
17 + "name": {
18 + "name": "name",
19 + "type": "text",
20 + "primaryKey": false,
21 + "notNull": true
22 + },
23 + "category_slugs": {
24 + "name": "category_slugs",
25 + "type": "text[]",
26 + "primaryKey": false,
27 + "notNull": true,
28 + "default": "'{}'::text[]"
29 + },
30 + "country": {
31 + "name": "country",
32 + "type": "text",
33 + "primaryKey": false,
34 + "notNull": false
35 + },
36 + "founded_year": {
37 + "name": "founded_year",
38 + "type": "integer",
39 + "primaryKey": false,
40 + "notNull": false
41 + },
42 + "metadata": {
43 + "name": "metadata",
44 + "type": "jsonb",
45 + "primaryKey": false,
46 + "notNull": true,
47 + "default": "'{}'::jsonb"
48 + },
49 + "created_at": {
50 + "name": "created_at",
51 + "type": "timestamp with time zone",
52 + "primaryKey": false,
53 + "notNull": true,
54 + "default": "now()"
55 + }
56 + },
57 + "indexes": {
58 + "brands_name_idx": {
59 + "name": "brands_name_idx",
60 + "columns": [
61 + {
62 + "expression": "name",
63 + "isExpression": false,
64 + "asc": true,
65 + "nulls": "last"
66 + }
67 + ],
68 + "isUnique": false,
69 + "concurrently": false,
70 + "method": "btree",
71 + "with": {}
72 + }
73 + },
74 + "foreignKeys": {},
75 + "compositePrimaryKeys": {},
76 + "uniqueConstraints": {},
77 + "policies": {},
78 + "checkConstraints": {},
79 + "isRLSEnabled": false
80 + },
81 + "public.categories": {
82 + "name": "categories",
83 + "schema": "",
84 + "columns": {
85 + "slug": {
86 + "name": "slug",
87 + "type": "text",
88 + "primaryKey": true,
89 + "notNull": true
90 + },
91 + "parent_slug": {
92 + "name": "parent_slug",
93 + "type": "text",
94 + "primaryKey": false,
95 + "notNull": false
96 + },
97 + "family_slug": {
98 + "name": "family_slug",
99 + "type": "text",
100 + "primaryKey": false,
101 + "notNull": true
102 + },
103 + "name": {
104 + "name": "name",
105 + "type": "text",
106 + "primaryKey": false,
107 + "notNull": true
108 + },
109 + "short_name": {
110 + "name": "short_name",
111 + "type": "text",
112 + "primaryKey": false,
113 + "notNull": false
114 + },
115 + "description": {
116 + "name": "description",
117 + "type": "text",
118 + "primaryKey": false,
119 + "notNull": false
120 + },
121 + "level": {
122 + "name": "level",
123 + "type": "integer",
124 + "primaryKey": false,
125 + "notNull": true,
126 + "default": 0
127 + },
128 + "phase": {
129 + "name": "phase",
130 + "type": "integer",
131 + "primaryKey": false,
132 + "notNull": true,
133 + "default": 3
134 + },
135 + "active": {
136 + "name": "active",
137 + "type": "boolean",
138 + "primaryKey": false,
139 + "notNull": true,
140 + "default": true
141 + },
142 + "condition_scale": {
143 + "name": "condition_scale",
144 + "type": "text",
145 + "primaryKey": false,
146 + "notNull": false
147 + },
148 + "graders": {
149 + "name": "graders",
150 + "type": "text[]",
151 + "primaryKey": false,
152 + "notNull": true,
153 + "default": "'{}'::text[]"
154 + },
155 + "index_ticker": {
156 + "name": "index_ticker",
157 + "type": "text",
158 + "primaryKey": false,
159 + "notNull": false
160 + },
161 + "attribute_schema": {
162 + "name": "attribute_schema",
163 + "type": "jsonb",
164 + "primaryKey": false,
165 + "notNull": true,
166 + "default": "'{}'::jsonb"
167 + },
168 + "sort_order": {
169 + "name": "sort_order",
170 + "type": "integer",
171 + "primaryKey": false,
172 + "notNull": true,
173 + "default": 0
174 + },
175 + "compliance_flags": {
176 + "name": "compliance_flags",
177 + "type": "text[]",
178 + "primaryKey": false,
179 + "notNull": true,
180 + "default": "'{}'::text[]"
181 + },
182 + "icon": {
183 + "name": "icon",
184 + "type": "text",
185 + "primaryKey": false,
186 + "notNull": false
187 + },
188 + "created_at": {
189 + "name": "created_at",
190 + "type": "timestamp with time zone",
191 + "primaryKey": false,
192 + "notNull": true,
193 + "default": "now()"
194 + },
195 + "updated_at": {
196 + "name": "updated_at",
197 + "type": "timestamp with time zone",
198 + "primaryKey": false,
199 + "notNull": true,
200 + "default": "now()"
201 + }
202 + },
203 + "indexes": {
204 + "categories_parent_idx": {
205 + "name": "categories_parent_idx",
206 + "columns": [
207 + {
208 + "expression": "parent_slug",
209 + "isExpression": false,
210 + "asc": true,
211 + "nulls": "last"
212 + }
213 + ],
214 + "isUnique": false,
215 + "concurrently": false,
216 + "method": "btree",
217 + "with": {}
218 + },
219 + "categories_family_idx": {
220 + "name": "categories_family_idx",
221 + "columns": [
222 + {
223 + "expression": "family_slug",
224 + "isExpression": false,
225 + "asc": true,
226 + "nulls": "last"
227 + }
228 + ],
229 + "isUnique": false,
230 + "concurrently": false,
231 + "method": "btree",
232 + "with": {}
233 + }
234 + },
235 + "foreignKeys": {},
236 + "compositePrimaryKeys": {},
237 + "uniqueConstraints": {},
238 + "policies": {},
239 + "checkConstraints": {},
240 + "isRLSEnabled": false
241 + },
242 + "public.franchises": {
243 + "name": "franchises",
244 + "schema": "",
245 + "columns": {
246 + "slug": {
247 + "name": "slug",
248 + "type": "text",
249 + "primaryKey": true,
250 + "notNull": true
251 + },
252 + "name": {
253 + "name": "name",
254 + "type": "text",
255 + "primaryKey": false,
256 + "notNull": true
257 + },
258 + "owner": {
259 + "name": "owner",
260 + "type": "text",
261 + "primaryKey": false,
262 + "notNull": false
263 + },
264 + "category_slugs": {
265 + "name": "category_slugs",
266 + "type": "text[]",
267 + "primaryKey": false,
268 + "notNull": true,
269 + "default": "'{}'::text[]"
270 + },
271 + "metadata": {
272 + "name": "metadata",
273 + "type": "jsonb",
274 + "primaryKey": false,
275 + "notNull": true,
276 + "default": "'{}'::jsonb"
277 + },
278 + "created_at": {
279 + "name": "created_at",
280 + "type": "timestamp with time zone",
281 + "primaryKey": false,
282 + "notNull": true,
283 + "default": "now()"
284 + }
285 + },
286 + "indexes": {},
287 + "foreignKeys": {},
288 + "compositePrimaryKeys": {},
289 + "uniqueConstraints": {},
290 + "policies": {},
291 + "checkConstraints": {},
292 + "isRLSEnabled": false
293 + },
294 + "public.graders": {
295 + "name": "graders",
296 + "schema": "",
297 + "columns": {
298 + "slug": {
299 + "name": "slug",
300 + "type": "text",
301 + "primaryKey": true,
302 + "notNull": true
303 + },
304 + "name": {
305 + "name": "name",
306 + "type": "text",
307 + "primaryKey": false,
308 + "notNull": true
309 + },
310 + "category_slugs": {
311 + "name": "category_slugs",
312 + "type": "text[]",
313 + "primaryKey": false,
314 + "notNull": true,
315 + "default": "'{}'::text[]"
316 + },
317 + "scale": {
318 + "name": "scale",
319 + "type": "jsonb",
320 + "primaryKey": false,
321 + "notNull": true,
322 + "default": "'{}'::jsonb"
323 + },
324 + "population_url": {
325 + "name": "population_url",
326 + "type": "text",
327 + "primaryKey": false,
328 + "notNull": false
329 + },
330 + "verify_url": {
331 + "name": "verify_url",
332 + "type": "text",
333 + "primaryKey": false,
334 + "notNull": false
335 + },
336 + "active": {
337 + "name": "active",
338 + "type": "boolean",
339 + "primaryKey": false,
340 + "notNull": true,
341 + "default": true
342 + }
343 + },
344 + "indexes": {},
345 + "foreignKeys": {},
346 + "compositePrimaryKeys": {},
347 + "uniqueConstraints": {},
348 + "policies": {},
349 + "checkConstraints": {},
350 + "isRLSEnabled": false
351 + },
352 + "public.sets": {
353 + "name": "sets",
354 + "schema": "",
355 + "columns": {
356 + "slug": {
357 + "name": "slug",
358 + "type": "text",
359 + "primaryKey": true,
360 + "notNull": true
361 + },
362 + "name": {
363 + "name": "name",
364 + "type": "text",
365 + "primaryKey": false,
366 + "notNull": true
367 + },
368 + "code": {
369 + "name": "code",
370 + "type": "text",
371 + "primaryKey": false,
372 + "notNull": false
373 + },
374 + "category_slug": {
375 + "name": "category_slug",
376 + "type": "text",
377 + "primaryKey": false,
378 + "notNull": true
379 + },
380 + "franchise_slug": {
381 + "name": "franchise_slug",
382 + "type": "text",
383 + "primaryKey": false,
384 + "notNull": false
385 + },
386 + "brand_slug": {
387 + "name": "brand_slug",
388 + "type": "text",
389 + "primaryKey": false,
390 + "notNull": false
391 + },
392 + "release_year": {
393 + "name": "release_year",
394 + "type": "integer",
395 + "primaryKey": false,
396 + "notNull": false
397 + },
398 + "release_date": {
399 + "name": "release_date",
400 + "type": "text",
401 + "primaryKey": false,
402 + "notNull": false
403 + },
404 + "language": {
405 + "name": "language",
406 + "type": "text",
407 + "primaryKey": false,
408 + "notNull": false
409 + },
410 + "total_items": {
411 + "name": "total_items",
412 + "type": "integer",
413 + "primaryKey": false,
414 + "notNull": false
415 + },
416 + "identifiers": {
417 + "name": "identifiers",
418 + "type": "jsonb",
419 + "primaryKey": false,
420 + "notNull": true,
421 + "default": "'{}'::jsonb"
422 + },
423 + "metadata": {
424 + "name": "metadata",
425 + "type": "jsonb",
426 + "primaryKey": false,
427 + "notNull": true,
428 + "default": "'{}'::jsonb"
429 + },
430 + "created_at": {
431 + "name": "created_at",
432 + "type": "timestamp with time zone",
433 + "primaryKey": false,
434 + "notNull": true,
435 + "default": "now()"
436 + }
437 + },
438 + "indexes": {
439 + "sets_category_idx": {
440 + "name": "sets_category_idx",
441 + "columns": [
442 + {
443 + "expression": "category_slug",
444 + "isExpression": false,
445 + "asc": true,
446 + "nulls": "last"
447 + }
448 + ],
449 + "isUnique": false,
450 + "concurrently": false,
451 + "method": "btree",
452 + "with": {}
453 + },
454 + "sets_category_code_uq": {
455 + "name": "sets_category_code_uq",
456 + "columns": [
457 + {
458 + "expression": "category_slug",
459 + "isExpression": false,
460 + "asc": true,
461 + "nulls": "last"
462 + },
463 + {
464 + "expression": "code",
465 + "isExpression": false,
466 + "asc": true,
467 + "nulls": "last"
468 + }
469 + ],
470 + "isUnique": true,
471 + "concurrently": false,
472 + "method": "btree",
473 + "with": {}
474 + }
475 + },
476 + "foreignKeys": {},
477 + "compositePrimaryKeys": {},
478 + "uniqueConstraints": {},
479 + "policies": {},
480 + "checkConstraints": {},
481 + "isRLSEnabled": false
482 + },
483 + "public.taxonomy_proposals": {
484 + "name": "taxonomy_proposals",
485 + "schema": "",
486 + "columns": {
487 + "id": {
488 + "name": "id",
489 + "type": "text",
490 + "primaryKey": true,
491 + "notNull": true
492 + },
493 + "proposed_slug": {
494 + "name": "proposed_slug",
495 + "type": "text",
496 + "primaryKey": false,
497 + "notNull": true
498 + },
499 + "name": {
500 + "name": "name",
501 + "type": "text",
502 + "primaryKey": false,
503 + "notNull": true
504 + },
505 + "parent_slug": {
506 + "name": "parent_slug",
507 + "type": "text",
508 + "primaryKey": false,
509 + "notNull": false
510 + },
511 + "evidence": {
512 + "name": "evidence",
513 + "type": "jsonb",
514 + "primaryKey": false,
515 + "notNull": true,
516 + "default": "'{}'::jsonb"
517 + },
518 + "volume_estimate": {
519 + "name": "volume_estimate",
520 + "type": "integer",
521 + "primaryKey": false,
522 + "notNull": false
523 + },
524 + "status": {
525 + "name": "status",
526 + "type": "text",
527 + "primaryKey": false,
528 + "notNull": true,
529 + "default": "'pending'"
530 + },
531 + "decided_by": {
532 + "name": "decided_by",
533 + "type": "text",
534 + "primaryKey": false,
535 + "notNull": false
536 + },
537 + "decided_at": {
538 + "name": "decided_at",
539 + "type": "text",
540 + "primaryKey": false,
541 + "notNull": false
542 + },
543 + "created_at": {
544 + "name": "created_at",
545 + "type": "timestamp with time zone",
546 + "primaryKey": false,
547 + "notNull": true,
548 + "default": "now()"
549 + }
550 + },
551 + "indexes": {},
552 + "foreignKeys": {},
553 + "compositePrimaryKeys": {},
554 + "uniqueConstraints": {},
555 + "policies": {},
556 + "checkConstraints": {},
557 + "isRLSEnabled": false
558 + },
559 + "public.connector_health": {
560 + "name": "connector_health",
561 + "schema": "",
562 + "columns": {
563 + "connector_id": {
564 + "name": "connector_id",
565 + "type": "text",
566 + "primaryKey": true,
567 + "notNull": true
568 + },
569 + "computed_at": {
570 + "name": "computed_at",
571 + "type": "timestamp with time zone",
572 + "primaryKey": false,
573 + "notNull": true
574 + },
575 + "status": {
576 + "name": "status",
577 + "type": "text",
578 + "primaryKey": false,
579 + "notNull": true
580 + },
581 + "health": {
582 + "name": "health",
583 + "type": "jsonb",
584 + "primaryKey": false,
585 + "notNull": true,
586 + "default": "'{}'::jsonb"
587 + }
588 + },
589 + "indexes": {},
590 + "foreignKeys": {},
591 + "compositePrimaryKeys": {},
592 + "uniqueConstraints": {},
593 + "policies": {},
594 + "checkConstraints": {},
595 + "isRLSEnabled": false
596 + },
597 + "public.connector_runs": {
598 + "name": "connector_runs",
599 + "schema": "",
600 + "columns": {
601 + "id": {
602 + "name": "id",
603 + "type": "text",
604 + "primaryKey": true,
605 + "notNull": true
606 + },
607 + "connector_id": {
608 + "name": "connector_id",
609 + "type": "text",
610 + "primaryKey": false,
611 + "notNull": true
612 + },
613 + "trigger": {
614 + "name": "trigger",
615 + "type": "text",
616 + "primaryKey": false,
617 + "notNull": true,
618 + "default": "'schedule'"
619 + },
620 + "started_at": {
621 + "name": "started_at",
622 + "type": "timestamp with time zone",
623 + "primaryKey": false,
624 + "notNull": true
625 + },
626 + "finished_at": {
627 + "name": "finished_at",
628 + "type": "timestamp with time zone",
629 + "primaryKey": false,
630 + "notNull": false
631 + },
632 + "status": {
633 + "name": "status",
634 + "type": "text",
635 + "primaryKey": false,
636 + "notNull": true,
637 + "default": "'running'"
638 + },
639 + "pages_attempted": {
640 + "name": "pages_attempted",
641 + "type": "integer",
642 + "primaryKey": false,
643 + "notNull": true,
644 + "default": 0
645 + },
646 + "pages_success": {
647 + "name": "pages_success",
648 + "type": "integer",
649 + "primaryKey": false,
650 + "notNull": true,
651 + "default": 0
652 + },
653 + "records_raw": {
654 + "name": "records_raw",
655 + "type": "integer",
656 + "primaryKey": false,
657 + "notNull": true,
658 + "default": 0
659 + },
660 + "records_normalized": {
661 + "name": "records_normalized",
662 + "type": "integer",
663 + "primaryKey": false,
664 + "notNull": true,
665 + "default": 0
666 + },
667 + "records_duplicate": {
668 + "name": "records_duplicate",
669 + "type": "integer",
670 + "primaryKey": false,
671 + "notNull": true,
672 + "default": 0
673 + },
674 + "records_rejected": {
675 + "name": "records_rejected",
676 + "type": "integer",
677 + "primaryKey": false,
678 + "notNull": true,
679 + "default": 0
680 + },
681 + "engine_stats": {
682 + "name": "engine_stats",
683 + "type": "jsonb",
684 + "primaryKey": false,
685 + "notNull": true,
686 + "default": "'{}'::jsonb"
687 + },
688 + "anomalies": {
689 + "name": "anomalies",
690 + "type": "jsonb",
691 + "primaryKey": false,
692 + "notNull": true,
693 + "default": "'[]'::jsonb"
694 + },
695 + "error": {
696 + "name": "error",
697 + "type": "text",
698 + "primaryKey": false,
699 + "notNull": false
700 + },
701 + "cost_credits": {
702 + "name": "cost_credits",
703 + "type": "real",
704 + "primaryKey": false,
705 + "notNull": true,
706 + "default": 0
707 + },
708 + "cost_usd_est": {
709 + "name": "cost_usd_est",
710 + "type": "real",
711 + "primaryKey": false,
712 + "notNull": true,
713 + "default": 0
714 + },
715 + "cursor": {
716 + "name": "cursor",
717 + "type": "jsonb",
718 + "primaryKey": false,
719 + "notNull": true,
720 + "default": "'{}'::jsonb"
721 + }
722 + },
723 + "indexes": {
724 + "connector_runs_connector_started_idx": {
725 + "name": "connector_runs_connector_started_idx",
726 + "columns": [
727 + {
728 + "expression": "connector_id",
729 + "isExpression": false,
730 + "asc": true,
731 + "nulls": "last"
732 + },
733 + {
734 + "expression": "started_at",
735 + "isExpression": false,
736 + "asc": true,
737 + "nulls": "last"
738 + }
739 + ],
740 + "isUnique": false,
741 + "concurrently": false,
742 + "method": "btree",
743 + "with": {}
744 + }
745 + },
746 + "foreignKeys": {},
747 + "compositePrimaryKeys": {},
748 + "uniqueConstraints": {},
749 + "policies": {},
750 + "checkConstraints": {},
751 + "isRLSEnabled": false
752 + },
753 + "public.connectors": {
754 + "name": "connectors",
755 + "schema": "",
756 + "columns": {
757 + "id": {
758 + "name": "id",
759 + "type": "text",
760 + "primaryKey": true,
761 + "notNull": true
762 + },
763 + "source_id": {
764 + "name": "source_id",
765 + "type": "text",
766 + "primaryKey": false,
767 + "notNull": true
768 + },
769 + "display_name": {
770 + "name": "display_name",
771 + "type": "text",
772 + "primaryKey": false,
773 + "notNull": true
774 + },
775 + "engine_priority": {
776 + "name": "engine_priority",
777 + "type": "text[]",
778 + "primaryKey": false,
779 + "notNull": true,
780 + "default": "'{}'::text[]"
781 + },
782 + "categories": {
783 + "name": "categories",
784 + "type": "text[]",
785 + "primaryKey": false,
786 + "notNull": true,
787 + "default": "'{}'::text[]"
788 + },
789 + "regions": {
790 + "name": "regions",
791 + "type": "text[]",
792 + "primaryKey": false,
793 + "notNull": true,
794 + "default": "'{}'::text[]"
795 + },
796 + "languages": {
797 + "name": "languages",
798 + "type": "text[]",
799 + "primaryKey": false,
800 + "notNull": true,
801 + "default": "'{}'::text[]"
802 + },
803 + "currency": {
804 + "name": "currency",
805 + "type": "text[]",
806 + "primaryKey": false,
807 + "notNull": true,
808 + "default": "'{}'::text[]"
809 + },
810 + "supports_listings": {
811 + "name": "supports_listings",
812 + "type": "boolean",
813 + "primaryKey": false,
814 + "notNull": true,
815 + "default": false
816 + },
817 + "supports_sold": {
818 + "name": "supports_sold",
819 + "type": "boolean",
820 + "primaryKey": false,
821 + "notNull": true,
822 + "default": false
823 + },
824 + "supports_auctions": {
825 + "name": "supports_auctions",
826 + "type": "boolean",
827 + "primaryKey": false,
828 + "notNull": true,
829 + "default": false
830 + },
831 + "supports_images": {
832 + "name": "supports_images",
833 + "type": "boolean",
834 + "primaryKey": false,
835 + "notNull": true,
836 + "default": true
837 + },
838 + "supports_catalog": {
839 + "name": "supports_catalog",
840 + "type": "boolean",
841 + "primaryKey": false,
842 + "notNull": true,
843 + "default": false
844 + },
845 + "supports_population": {
846 + "name": "supports_population",
847 + "type": "boolean",
848 + "primaryKey": false,
849 + "notNull": true,
850 + "default": false
851 + },
852 + "refresh_frequency_minutes": {
853 + "name": "refresh_frequency_minutes",
854 + "type": "integer",
855 + "primaryKey": false,
856 + "notNull": true,
857 + "default": 1440
858 + },
859 + "priority": {
860 + "name": "priority",
861 + "type": "text",
862 + "primaryKey": false,
863 + "notNull": true,
864 + "default": "'medium'"
865 + },
866 + "status": {
867 + "name": "status",
868 + "type": "text",
869 + "primaryKey": false,
870 + "notNull": true,
871 + "default": "'active'"
872 + },
873 + "schema_version": {
874 + "name": "schema_version",
875 + "type": "text",
876 + "primaryKey": false,
877 + "notNull": true,
878 + "default": "'1.0'"
879 + },
880 + "connector_version": {
881 + "name": "connector_version",
882 + "type": "text",
883 + "primaryKey": false,
884 + "notNull": true,
885 + "default": "'1.0.0'"
886 + },
887 + "config": {
888 + "name": "config",
889 + "type": "jsonb",
890 + "primaryKey": false,
891 + "notNull": true,
892 + "default": "'{}'::jsonb"
893 + },
894 + "last_run_at": {
895 + "name": "last_run_at",
896 + "type": "timestamp with time zone",
897 + "primaryKey": false,
898 + "notNull": false
899 + },
900 + "last_success_at": {
901 + "name": "last_success_at",
902 + "type": "timestamp with time zone",
903 + "primaryKey": false,
904 + "notNull": false
905 + },
906 + "next_run_at": {
907 + "name": "next_run_at",
908 + "type": "timestamp with time zone",
909 + "primaryKey": false,
910 + "notNull": false
911 + },
912 + "created_at": {
913 + "name": "created_at",
914 + "type": "timestamp with time zone",
915 + "primaryKey": false,
916 + "notNull": true,
917 + "default": "now()"
918 + },
919 + "updated_at": {
920 + "name": "updated_at",
921 + "type": "timestamp with time zone",
922 + "primaryKey": false,
923 + "notNull": true,
924 + "default": "now()"
925 + }
926 + },
927 + "indexes": {},
928 + "foreignKeys": {},
929 + "compositePrimaryKeys": {},
930 + "uniqueConstraints": {},
931 + "policies": {},
932 + "checkConstraints": {},
933 + "isRLSEnabled": false
934 + },
935 + "public.costs": {
936 + "name": "costs",
937 + "schema": "",
938 + "columns": {
939 + "id": {
940 + "name": "id",
941 + "type": "text",
942 + "primaryKey": true,
943 + "notNull": true
944 + },
945 + "occurred_at": {
946 + "name": "occurred_at",
947 + "type": "timestamp with time zone",
948 + "primaryKey": false,
949 + "notNull": true
950 + },
951 + "kind": {
952 + "name": "kind",
953 + "type": "text",
954 + "primaryKey": false,
955 + "notNull": true
956 + },
957 + "provider": {
958 + "name": "provider",
959 + "type": "text",
960 + "primaryKey": false,
961 + "notNull": false
962 + },
963 + "connector_id": {
964 + "name": "connector_id",
965 + "type": "text",
966 + "primaryKey": false,
967 + "notNull": false
968 + },
969 + "category_slug": {
970 + "name": "category_slug",
971 + "type": "text",
972 + "primaryKey": false,
973 + "notNull": false
974 + },
975 + "endpoint": {
976 + "name": "endpoint",
977 + "type": "text",
978 + "primaryKey": false,
979 + "notNull": false
980 + },
981 + "user_id": {
982 + "name": "user_id",
983 + "type": "text",
984 + "primaryKey": false,
985 + "notNull": false
986 + },
987 + "units": {
988 + "name": "units",
989 + "type": "real",
990 + "primaryKey": false,
991 + "notNull": true,
992 + "default": 1
993 + },
994 + "credits": {
995 + "name": "credits",
996 + "type": "real",
997 + "primaryKey": false,
998 + "notNull": true,
999 + "default": 0
1000 + },
1001 + "usd_est": {
1002 + "name": "usd_est",
1003 + "type": "real",
1004 + "primaryKey": false,
1005 + "notNull": true,
1006 + "default": 0
1007 + },
1008 + "metadata": {
1009 + "name": "metadata",
1010 + "type": "jsonb",
1011 + "primaryKey": false,
1012 + "notNull": true,
1013 + "default": "'{}'::jsonb"
1014 + }
1015 + },
1016 + "indexes": {
1017 + "costs_occurred_idx": {
1018 + "name": "costs_occurred_idx",
1019 + "columns": [
1020 + {
1021 + "expression": "occurred_at",
1022 + "isExpression": false,
1023 + "asc": true,
1024 + "nulls": "last"
1025 + }
1026 + ],
1027 + "isUnique": false,
1028 + "concurrently": false,
1029 + "method": "btree",
1030 + "with": {}
1031 + },
1032 + "costs_connector_idx": {
1033 + "name": "costs_connector_idx",
1034 + "columns": [
1035 + {
1036 + "expression": "connector_id",
1037 + "isExpression": false,
1038 + "asc": true,
1039 + "nulls": "last"
1040 + }
1041 + ],
1042 + "isUnique": false,
1043 + "concurrently": false,
1044 + "method": "btree",
1045 + "with": {}
1046 + }
1047 + },
1048 + "foreignKeys": {},
1049 + "compositePrimaryKeys": {},
1050 + "uniqueConstraints": {},
1051 + "policies": {},
1052 + "checkConstraints": {},
1053 + "isRLSEnabled": false
1054 + },
1055 + "public.crawl_state": {
1056 + "name": "crawl_state",
1057 + "schema": "",
1058 + "columns": {
1059 + "url_hash": {
1060 + "name": "url_hash",
1061 + "type": "text",
1062 + "primaryKey": true,
1063 + "notNull": true
1064 + },
1065 + "connector_id": {
1066 + "name": "connector_id",
1067 + "type": "text",
1068 + "primaryKey": false,
1069 + "notNull": true
1070 + },
1071 + "url": {
1072 + "name": "url",
1073 + "type": "text",
1074 + "primaryKey": false,
1075 + "notNull": true
1076 + },
1077 + "etag": {
1078 + "name": "etag",
1079 + "type": "text",
1080 + "primaryKey": false,
1081 + "notNull": false
1082 + },
1083 + "last_modified": {
1084 + "name": "last_modified",
1085 + "type": "text",
1086 + "primaryKey": false,
1087 + "notNull": false
1088 + },
1089 + "content_hash": {
1090 + "name": "content_hash",
1091 + "type": "text",
1092 + "primaryKey": false,
1093 + "notNull": false
1094 + },
1095 + "last_fetched_at": {
1096 + "name": "last_fetched_at",
1097 + "type": "timestamp with time zone",
1098 + "primaryKey": false,
1099 + "notNull": false
1100 + },
1101 + "last_changed_at": {
1102 + "name": "last_changed_at",
1103 + "type": "timestamp with time zone",
1104 + "primaryKey": false,
1105 + "notNull": false
1106 + },
1107 + "fetch_count": {
1108 + "name": "fetch_count",
1109 + "type": "integer",
1110 + "primaryKey": false,
1111 + "notNull": true,
1112 + "default": 0
1113 + },
1114 + "change_count": {
1115 + "name": "change_count",
1116 + "type": "integer",
1117 + "primaryKey": false,
1118 + "notNull": true,
1119 + "default": 0
1120 + },
1121 + "change_interval_hours": {
1122 + "name": "change_interval_hours",
1123 + "type": "real",
1124 + "primaryKey": false,
1125 + "notNull": false
1126 + },
1127 + "next_fetch_at": {
1128 + "name": "next_fetch_at",
1129 + "type": "timestamp with time zone",
1130 + "primaryKey": false,
1131 + "notNull": false
1132 + },
1133 + "last_status": {
1134 + "name": "last_status",
1135 + "type": "integer",
1136 + "primaryKey": false,
1137 + "notNull": false
1138 + },
1139 + "failures": {
1140 + "name": "failures",
1141 + "type": "integer",
1142 + "primaryKey": false,
1143 + "notNull": true,
1144 + "default": 0
1145 + }
1146 + },
1147 + "indexes": {
1148 + "crawl_state_next_idx": {
1149 + "name": "crawl_state_next_idx",
1150 + "columns": [
1151 + {
1152 + "expression": "connector_id",
1153 + "isExpression": false,
1154 + "asc": true,
1155 + "nulls": "last"
1156 + },
1157 + {
1158 + "expression": "next_fetch_at",
1159 + "isExpression": false,
1160 + "asc": true,
1161 + "nulls": "last"
1162 + }
1163 + ],
1164 + "isUnique": false,
1165 + "concurrently": false,
1166 + "method": "btree",
1167 + "with": {}
1168 + }
1169 + },
1170 + "foreignKeys": {},
1171 + "compositePrimaryKeys": {},
1172 + "uniqueConstraints": {},
1173 + "policies": {},
1174 + "checkConstraints": {},
1175 + "isRLSEnabled": false
1176 + },
1177 + "public.sources": {
1178 + "name": "sources",
1179 + "schema": "",
1180 + "columns": {
1181 + "id": {
1182 + "name": "id",
1183 + "type": "text",
1184 + "primaryKey": true,
1185 + "notNull": true
1186 + },
1187 + "name": {
1188 + "name": "name",
1189 + "type": "text",
1190 + "primaryKey": false,
1191 + "notNull": true
1192 + },
1193 + "source_type": {
1194 + "name": "source_type",
1195 + "type": "text",
1196 + "primaryKey": false,
1197 + "notNull": true
1198 + },
1199 + "url": {
1200 + "name": "url",
1201 + "type": "text",
1202 + "primaryKey": false,
1203 + "notNull": false
1204 + },
1205 + "countries": {
1206 + "name": "countries",
1207 + "type": "text[]",
1208 + "primaryKey": false,
1209 + "notNull": true,
1210 + "default": "'{}'::text[]"
1211 + },
1212 + "languages": {
1213 + "name": "languages",
1214 + "type": "text[]",
1215 + "primaryKey": false,
1216 + "notNull": true,
1217 + "default": "'{}'::text[]"
1218 + },
1219 + "currencies": {
1220 + "name": "currencies",
1221 + "type": "text[]",
1222 + "primaryKey": false,
1223 + "notNull": true,
1224 + "default": "'{}'::text[]"
1225 + },
1226 + "trust_score": {
1227 + "name": "trust_score",
1228 + "type": "numeric(8, 6)",
1229 + "primaryKey": false,
1230 + "notNull": true,
1231 + "default": 0.5
1232 + },
1233 + "trust_factors": {
1234 + "name": "trust_factors",
1235 + "type": "jsonb",
1236 + "primaryKey": false,
1237 + "notNull": true,
1238 + "default": "'{}'::jsonb"
1239 + },
1240 + "attribution_required": {
1241 + "name": "attribution_required",
1242 + "type": "boolean",
1243 + "primaryKey": false,
1244 + "notNull": true,
1245 + "default": true
1246 + },
1247 + "terms_url": {
1248 + "name": "terms_url",
1249 + "type": "text",
1250 + "primaryKey": false,
1251 + "notNull": false
1252 + },
1253 + "active": {
1254 + "name": "active",
1255 + "type": "boolean",
1256 + "primaryKey": false,
1257 + "notNull": true,
1258 + "default": true
1259 + },
1260 + "created_at": {
1261 + "name": "created_at",
1262 + "type": "timestamp with time zone",
1263 + "primaryKey": false,
1264 + "notNull": true,
1265 + "default": "now()"
1266 + },
1267 + "updated_at": {
1268 + "name": "updated_at",
1269 + "type": "timestamp with time zone",
1270 + "primaryKey": false,
1271 + "notNull": true,
1272 + "default": "now()"
1273 + }
1274 + },
1275 + "indexes": {},
1276 + "foreignKeys": {},
1277 + "compositePrimaryKeys": {},
1278 + "uniqueConstraints": {},
1279 + "policies": {},
1280 + "checkConstraints": {},
1281 + "isRLSEnabled": false
1282 + },
1283 + "public.audit_log": {
1284 + "name": "audit_log",
1285 + "schema": "",
1286 + "columns": {
1287 + "id": {
1288 + "name": "id",
1289 + "type": "text",
1290 + "primaryKey": true,
1291 + "notNull": true
1292 + },
1293 + "entity_type": {
1294 + "name": "entity_type",
1295 + "type": "text",
1296 + "primaryKey": false,
1297 + "notNull": true
1298 + },
1299 + "entity_id": {
1300 + "name": "entity_id",
1301 + "type": "text",
1302 + "primaryKey": false,
1303 + "notNull": true
1304 + },
1305 + "action": {
1306 + "name": "action",
1307 + "type": "text",
1308 + "primaryKey": false,
1309 + "notNull": true
1310 + },
1311 + "reason": {
1312 + "name": "reason",
1313 + "type": "text",
1314 + "primaryKey": false,
1315 + "notNull": true
1316 + },
1317 + "actor": {
1318 + "name": "actor",
1319 + "type": "text",
1320 + "primaryKey": false,
1321 + "notNull": true,
1322 + "default": "'system'"
1323 + },
1324 + "details": {
1325 + "name": "details",
1326 + "type": "jsonb",
1327 + "primaryKey": false,
1328 + "notNull": true,
1329 + "default": "'{}'::jsonb"
1330 + },
1331 + "created_at": {
1332 + "name": "created_at",
1333 + "type": "timestamp with time zone",
1334 + "primaryKey": false,
1335 + "notNull": true,
1336 + "default": "now()"
1337 + }
1338 + },
1339 + "indexes": {
1340 + "audit_entity_idx": {
1341 + "name": "audit_entity_idx",
1342 + "columns": [
1343 + {
1344 + "expression": "entity_type",
1345 + "isExpression": false,
1346 + "asc": true,
1347 + "nulls": "last"
1348 + },
1349 + {
1350 + "expression": "entity_id",
1351 + "isExpression": false,
1352 + "asc": true,
1353 + "nulls": "last"
1354 + }
1355 + ],
1356 + "isUnique": false,
1357 + "concurrently": false,
1358 + "method": "btree",
1359 + "with": {}
1360 + }
1361 + },
1362 + "foreignKeys": {},
1363 + "compositePrimaryKeys": {},
1364 + "uniqueConstraints": {},
1365 + "policies": {},
1366 + "checkConstraints": {},
1367 + "isRLSEnabled": false
1368 + },
1369 + "public.events": {
1370 + "name": "events",
1371 + "schema": "",
1372 + "columns": {
1373 + "id": {
1374 + "name": "id",
1375 + "type": "text",
1376 + "primaryKey": true,
1377 + "notNull": true
1378 + },
1379 + "type": {
1380 + "name": "type",
1381 + "type": "text",
1382 + "primaryKey": false,
1383 + "notNull": true
1384 + },
1385 + "entity_type": {
1386 + "name": "entity_type",
1387 + "type": "text",
1388 + "primaryKey": false,
1389 + "notNull": false
1390 + },
1391 + "entity_id": {
1392 + "name": "entity_id",
1393 + "type": "text",
1394 + "primaryKey": false,
1395 + "notNull": false
1396 + },
1397 + "payload": {
1398 + "name": "payload",
1399 + "type": "jsonb",
1400 + "primaryKey": false,
1401 + "notNull": true,
1402 + "default": "'{}'::jsonb"
1403 + },
1404 + "created_at": {
1405 + "name": "created_at",
1406 + "type": "timestamp with time zone",
1407 + "primaryKey": false,
1408 + "notNull": true,
1409 + "default": "now()"
1410 + }
1411 + },
1412 + "indexes": {
1413 + "events_type_created_idx": {
1414 + "name": "events_type_created_idx",
1415 + "columns": [
1416 + {
1417 + "expression": "type",
1418 + "isExpression": false,
1419 + "asc": true,
1420 + "nulls": "last"
1421 + },
1422 + {
1423 + "expression": "created_at",
1424 + "isExpression": false,
1425 + "asc": true,
1426 + "nulls": "last"
1427 + }
1428 + ],
1429 + "isUnique": false,
1430 + "concurrently": false,
1431 + "method": "btree",
1432 + "with": {}
1433 + },
1434 + "events_entity_idx": {
1435 + "name": "events_entity_idx",
1436 + "columns": [
1437 + {
1438 + "expression": "entity_type",
1439 + "isExpression": false,
1440 + "asc": true,
1441 + "nulls": "last"
1442 + },
1443 + {
1444 + "expression": "entity_id",
1445 + "isExpression": false,
1446 + "asc": true,
1447 + "nulls": "last"
1448 + }
1449 + ],
1450 + "isUnique": false,
1451 + "concurrently": false,
1452 + "method": "btree",
1453 + "with": {}
1454 + }
1455 + },
1456 + "foreignKeys": {},
1457 + "compositePrimaryKeys": {},
1458 + "uniqueConstraints": {},
1459 + "policies": {},
1460 + "checkConstraints": {},
1461 + "isRLSEnabled": false
1462 + },
1463 + "public.normalized_records": {
1464 + "name": "normalized_records",
1465 + "schema": "",
1466 + "columns": {
1467 + "id": {
1468 + "name": "id",
1469 + "type": "text",
1470 + "primaryKey": true,
1471 + "notNull": true
1472 + },
1473 + "raw_record_id": {
1474 + "name": "raw_record_id",
1475 + "type": "text",
1476 + "primaryKey": false,
1477 + "notNull": true
1478 + },
1479 + "connector_id": {
1480 + "name": "connector_id",
1481 + "type": "text",
1482 + "primaryKey": false,
1483 + "notNull": true
1484 + },
1485 + "source_id": {
1486 + "name": "source_id",
1487 + "type": "text",
1488 + "primaryKey": false,
1489 + "notNull": true
1490 + },
1491 + "kind": {
1492 + "name": "kind",
1493 + "type": "text",
1494 + "primaryKey": false,
1495 + "notNull": true
1496 + },
1497 + "payload": {
1498 + "name": "payload",
1499 + "type": "jsonb",
1500 + "primaryKey": false,
1501 + "notNull": true
1502 + },
1503 + "asset_id": {
1504 + "name": "asset_id",
1505 + "type": "text",
1506 + "primaryKey": false,
1507 + "notNull": false
1508 + },
1509 + "variant_id": {
1510 + "name": "variant_id",
1511 + "type": "text",
1512 + "primaryKey": false,
1513 + "notNull": false
1514 + },
1515 + "match_method": {
1516 + "name": "match_method",
1517 + "type": "text",
1518 + "primaryKey": false,
1519 + "notNull": false
1520 + },
1521 + "match_confidence": {
1522 + "name": "match_confidence",
1523 + "type": "numeric(8, 6)",
1524 + "primaryKey": false,
1525 + "notNull": false
1526 + },
1527 + "status": {
1528 + "name": "status",
1529 + "type": "text",
1530 + "primaryKey": false,
1531 + "notNull": true,
1532 + "default": "'pending'"
1533 + },
1534 + "reject_reason": {
1535 + "name": "reject_reason",
1536 + "type": "text",
1537 + "primaryKey": false,
1538 + "notNull": false
1539 + },
1540 + "target_id": {
1541 + "name": "target_id",
1542 + "type": "text",
1543 + "primaryKey": false,
1544 + "notNull": false
1545 + },
1546 + "created_at": {
1547 + "name": "created_at",
1548 + "type": "timestamp with time zone",
1549 + "primaryKey": false,
1550 + "notNull": true,
1551 + "default": "now()"
1552 + },
1553 + "processed_at": {
1554 + "name": "processed_at",
1555 + "type": "timestamp with time zone",
1556 + "primaryKey": false,
1557 + "notNull": false
1558 + }
1559 + },
1560 + "indexes": {
1561 + "normalized_records_status_idx": {
1562 + "name": "normalized_records_status_idx",
1563 + "columns": [
1564 + {
1565 + "expression": "status",
1566 + "isExpression": false,
1567 + "asc": true,
1568 + "nulls": "last"
1569 + },
1570 + {
1571 + "expression": "created_at",
1572 + "isExpression": false,
1573 + "asc": true,
1574 + "nulls": "last"
1575 + }
1576 + ],
1577 + "isUnique": false,
1578 + "concurrently": false,
1579 + "method": "btree",
1580 + "with": {}
1581 + },
1582 + "normalized_records_asset_idx": {
1583 + "name": "normalized_records_asset_idx",
1584 + "columns": [
1585 + {
1586 + "expression": "asset_id",
1587 + "isExpression": false,
1588 + "asc": true,
1589 + "nulls": "last"
1590 + }
1591 + ],
1592 + "isUnique": false,
1593 + "concurrently": false,
1594 + "method": "btree",
1595 + "with": {}
1596 + },
1597 + "normalized_records_raw_uq": {
1598 + "name": "normalized_records_raw_uq",
1599 + "columns": [
1600 + {
1601 + "expression": "raw_record_id",
1602 + "isExpression": false,
1603 + "asc": true,
1604 + "nulls": "last"
1605 + }
1606 + ],
1607 + "isUnique": true,
1608 + "concurrently": false,
1609 + "method": "btree",
1610 + "with": {}
1611 + }
1612 + },
1613 + "foreignKeys": {},
1614 + "compositePrimaryKeys": {},
1615 + "uniqueConstraints": {},
1616 + "policies": {},
1617 + "checkConstraints": {},
1618 + "isRLSEnabled": false
1619 + },
1620 + "public.raw_records": {
1621 + "name": "raw_records",
1622 + "schema": "",
1623 + "columns": {
1624 + "id": {
1625 + "name": "id",
1626 + "type": "text",
1627 + "primaryKey": true,
1628 + "notNull": true
1629 + },
1630 + "connector_id": {
1631 + "name": "connector_id",
1632 + "type": "text",
1633 + "primaryKey": false,
1634 + "notNull": true
1635 + },
1636 + "source_id": {
1637 + "name": "source_id",
1638 + "type": "text",
1639 + "primaryKey": false,
1640 + "notNull": true
1641 + },
1642 + "run_id": {
1643 + "name": "run_id",
1644 + "type": "text",
1645 + "primaryKey": false,
1646 + "notNull": false
1647 + },
1648 + "engine": {
1649 + "name": "engine",
1650 + "type": "text",
1651 + "primaryKey": false,
1652 + "notNull": true
1653 + },
1654 + "url": {
1655 + "name": "url",
1656 + "type": "text",
1657 + "primaryKey": false,
1658 + "notNull": true
1659 + },
1660 + "external_id": {
1661 + "name": "external_id",
1662 + "type": "text",
1663 + "primaryKey": false,
1664 + "notNull": false
1665 + },
1666 + "kind": {
1667 + "name": "kind",
1668 + "type": "text",
1669 + "primaryKey": false,
1670 + "notNull": true
1671 + },
1672 + "fetched_at": {
1673 + "name": "fetched_at",
1674 + "type": "timestamp with time zone",
1675 + "primaryKey": false,
1676 + "notNull": true
1677 + },
1678 + "content_hash": {
1679 + "name": "content_hash",
1680 + "type": "text",
1681 + "primaryKey": false,
1682 + "notNull": true
1683 + },
1684 + "http_status": {
1685 + "name": "http_status",
1686 + "type": "integer",
1687 + "primaryKey": false,
1688 + "notNull": false
1689 + },
1690 + "payload": {
1691 + "name": "payload",
1692 + "type": "jsonb",
1693 + "primaryKey": false,
1694 + "notNull": true
1695 + },
1696 + "snapshot_ref": {
1697 + "name": "snapshot_ref",
1698 + "type": "text",
1699 + "primaryKey": false,
1700 + "notNull": false
1701 + },
1702 + "parser_version": {
1703 + "name": "parser_version",
1704 + "type": "text",
1705 + "primaryKey": false,
1706 + "notNull": true
1707 + },
1708 + "connector_version": {
1709 + "name": "connector_version",
1710 + "type": "text",
1711 + "primaryKey": false,
1712 + "notNull": true
1713 + },
1714 + "processed_at": {
1715 + "name": "processed_at",
1716 + "type": "timestamp with time zone",
1717 + "primaryKey": false,
1718 + "notNull": false
1719 + },
1720 + "process_error": {
1721 + "name": "process_error",
1722 + "type": "text",
1723 + "primaryKey": false,
1724 + "notNull": false
1725 + },
1726 + "created_at": {
1727 + "name": "created_at",
1728 + "type": "timestamp with time zone",
1729 + "primaryKey": false,
1730 + "notNull": true,
1731 + "default": "now()"
1732 + }
1733 + },
1734 + "indexes": {
1735 + "raw_records_connector_hash_uq": {
1736 + "name": "raw_records_connector_hash_uq",
1737 + "columns": [
1738 + {
1739 + "expression": "connector_id",
1740 + "isExpression": false,
1741 + "asc": true,
1742 + "nulls": "last"
1743 + },
1744 + {
1745 + "expression": "content_hash",
1746 + "isExpression": false,
1747 + "asc": true,
1748 + "nulls": "last"
1749 + }
1750 + ],
1751 + "isUnique": true,
1752 + "concurrently": false,
1753 + "method": "btree",
1754 + "with": {}
1755 + },
1756 + "raw_records_connector_fetched_idx": {
1757 + "name": "raw_records_connector_fetched_idx",
1758 + "columns": [
1759 + {
1760 + "expression": "connector_id",
1761 + "isExpression": false,
1762 + "asc": true,
1763 + "nulls": "last"
1764 + },
1765 + {
1766 + "expression": "fetched_at",
1767 + "isExpression": false,
1768 + "asc": true,
1769 + "nulls": "last"
1770 + }
1771 + ],
1772 + "isUnique": false,
1773 + "concurrently": false,
1774 + "method": "btree",
1775 + "with": {}
1776 + },
1777 + "raw_records_unprocessed_idx": {
1778 + "name": "raw_records_unprocessed_idx",
1779 + "columns": [
1780 + {
1781 + "expression": "processed_at",
1782 + "isExpression": false,
1783 + "asc": true,
1784 + "nulls": "last"
1785 + }
1786 + ],
1787 + "isUnique": false,
1788 + "concurrently": false,
1789 + "method": "btree",
1790 + "with": {}
1791 + },
1792 + "raw_records_external_idx": {
1793 + "name": "raw_records_external_idx",
1794 + "columns": [
1795 + {
1796 + "expression": "connector_id",
1797 + "isExpression": false,
1798 + "asc": true,
1799 + "nulls": "last"
1800 + },
1801 + {
1802 + "expression": "external_id",
1803 + "isExpression": false,
1804 + "asc": true,
1805 + "nulls": "last"
1806 + }
1807 + ],
1808 + "isUnique": false,
1809 + "concurrently": false,
1810 + "method": "btree",
1811 + "with": {}
1812 + }
1813 + },
1814 + "foreignKeys": {},
1815 + "compositePrimaryKeys": {},
1816 + "uniqueConstraints": {},
1817 + "policies": {},
1818 + "checkConstraints": {},
1819 + "isRLSEnabled": false
1820 + },
1821 + "public.asset_embeddings": {
1822 + "name": "asset_embeddings",
1823 + "schema": "",
1824 + "columns": {
1825 + "asset_id": {
1826 + "name": "asset_id",
1827 + "type": "text",
1828 + "primaryKey": true,
1829 + "notNull": true
1830 + },
1831 + "model": {
1832 + "name": "model",
1833 + "type": "text",
1834 + "primaryKey": false,
1835 + "notNull": true
1836 + },
1837 + "embedding": {
1838 + "name": "embedding",
1839 + "type": "vector(1536)",
1840 + "primaryKey": false,
1841 + "notNull": true
1842 + },
1843 + "updated_at": {
1844 + "name": "updated_at",
1845 + "type": "timestamp with time zone",
1846 + "primaryKey": false,
1847 + "notNull": true,
1848 + "default": "now()"
1849 + }
1850 + },
1851 + "indexes": {},
1852 + "foreignKeys": {},
1853 + "compositePrimaryKeys": {},
1854 + "uniqueConstraints": {},
1855 + "policies": {},
1856 + "checkConstraints": {},
1857 + "isRLSEnabled": false
1858 + },
1859 + "public.asset_stats": {
1860 + "name": "asset_stats",
1861 + "schema": "",
1862 + "columns": {
1863 + "asset_id": {
1864 + "name": "asset_id",
1865 + "type": "text",
1866 + "primaryKey": true,
1867 + "notNull": true
1868 + },
1869 + "riv_usd": {
1870 + "name": "riv_usd",
1871 + "type": "numeric(18, 4)",
1872 + "primaryKey": false,
1873 + "notNull": false
1874 + },
1875 + "riv_low_usd": {
1876 + "name": "riv_low_usd",
1877 + "type": "numeric(18, 4)",
1878 + "primaryKey": false,
1879 + "notNull": false
1880 + },
1881 + "riv_high_usd": {
1882 + "name": "riv_high_usd",
1883 + "type": "numeric(18, 4)",
1884 + "primaryKey": false,
1885 + "notNull": false
1886 + },
1887 + "riv_confidence": {
1888 + "name": "riv_confidence",
1889 + "type": "numeric(8, 6)",
1890 + "primaryKey": false,
1891 + "notNull": false
1892 + },
1893 + "riv_sample_size": {
1894 + "name": "riv_sample_size",
1895 + "type": "integer",
1896 + "primaryKey": false,
1897 + "notNull": true,
1898 + "default": 0
1899 + },
1900 + "riv_variant_id": {
1901 + "name": "riv_variant_id",
1902 + "type": "text",
1903 + "primaryKey": false,
1904 + "notNull": false
1905 + },
1906 + "latest_sale_usd": {
1907 + "name": "latest_sale_usd",
1908 + "type": "numeric(18, 4)",
1909 + "primaryKey": false,
1910 + "notNull": false
1911 + },
1912 + "latest_sale_at": {
1913 + "name": "latest_sale_at",
1914 + "type": "timestamp with time zone",
1915 + "primaryKey": false,
1916 + "notNull": false
1917 + },
1918 + "change_1d": {
1919 + "name": "change_1d",
1920 + "type": "numeric(8, 6)",
1921 + "primaryKey": false,
1922 + "notNull": false
1923 + },
1924 + "change_7d": {
1925 + "name": "change_7d",
1926 + "type": "numeric(8, 6)",
1927 + "primaryKey": false,
1928 + "notNull": false
1929 + },
1930 + "change_30d": {
1931 + "name": "change_30d",
1932 + "type": "numeric(8, 6)",
1933 + "primaryKey": false,
1934 + "notNull": false
1935 + },
1936 + "change_90d": {
1937 + "name": "change_90d",
1938 + "type": "numeric(8, 6)",
1939 + "primaryKey": false,
1940 + "notNull": false
1941 + },
1942 + "change_1y": {
1943 + "name": "change_1y",
1944 + "type": "numeric(8, 6)",
1945 + "primaryKey": false,
1946 + "notNull": false
1947 + },
1948 + "ath_usd": {
1949 + "name": "ath_usd",
1950 + "type": "numeric(18, 4)",
1951 + "primaryKey": false,
1952 + "notNull": false
1953 + },
1954 + "ath_at": {
1955 + "name": "ath_at",
1956 + "type": "timestamp with time zone",
1957 + "primaryKey": false,
1958 + "notNull": false
1959 + },
1960 + "atl_usd": {
1961 + "name": "atl_usd",
1962 + "type": "numeric(18, 4)",
1963 + "primaryKey": false,
1964 + "notNull": false
1965 + },
1966 + "atl_at": {
1967 + "name": "atl_at",
1968 + "type": "timestamp with time zone",
1969 + "primaryKey": false,
1970 + "notNull": false
1971 + },
1972 + "sales_count": {
1973 + "name": "sales_count",
1974 + "type": "integer",
1975 + "primaryKey": false,
1976 + "notNull": true,
1977 + "default": 0
1978 + },
1979 + "sales_30d": {
1980 + "name": "sales_30d",
1981 + "type": "integer",
1982 + "primaryKey": false,
1983 + "notNull": true,
1984 + "default": 0
1985 + },
1986 + "sales_1y": {
1987 + "name": "sales_1y",
1988 + "type": "integer",
1989 + "primaryKey": false,
1990 + "notNull": true,
1991 + "default": 0
1992 + },
1993 + "volume_30d_usd": {
1994 + "name": "volume_30d_usd",
1995 + "type": "numeric(18, 4)",
1996 + "primaryKey": false,
1997 + "notNull": false
1998 + },
1999 + "active_listings": {
2000 + "name": "active_listings",
2001 + "type": "integer",
2002 + "primaryKey": false,
2003 + "notNull": true,
2004 + "default": 0
2005 + },
2006 + "min_ask_usd": {
2007 + "name": "min_ask_usd",
2008 + "type": "numeric(18, 4)",
2009 + "primaryKey": false,
2010 + "notNull": false
2011 + },
2012 + "observations_count": {
2013 + "name": "observations_count",
2014 + "type": "integer",
2015 + "primaryKey": false,
2016 + "notNull": true,
2017 + "default": 0
2018 + },
2019 + "sources_count": {
2020 + "name": "sources_count",
2021 + "type": "integer",
2022 + "primaryKey": false,
2023 + "notNull": true,
2024 + "default": 0
2025 + },
2026 + "liquidity_score": {
2027 + "name": "liquidity_score",
2028 + "type": "real",
2029 + "primaryKey": false,
2030 + "notNull": false
2031 + },
2032 + "rarity_score": {
2033 + "name": "rarity_score",
2034 + "type": "real",
2035 + "primaryKey": false,
2036 + "notNull": false
2037 + },
2038 + "momentum_7d": {
2039 + "name": "momentum_7d",
2040 + "type": "real",
2041 + "primaryKey": false,
2042 + "notNull": false
2043 + },
2044 + "momentum_30d": {
2045 + "name": "momentum_30d",
2046 + "type": "real",
2047 + "primaryKey": false,
2048 + "notNull": false
2049 + },
2050 + "momentum_90d": {
2051 + "name": "momentum_90d",
2052 + "type": "real",
2053 + "primaryKey": false,
2054 + "notNull": false
2055 + },
2056 + "momentum_1y": {
2057 + "name": "momentum_1y",
2058 + "type": "real",
2059 + "primaryKey": false,
2060 + "notNull": false
2061 + },
2062 + "trending_score": {
2063 + "name": "trending_score",
2064 + "type": "real",
2065 + "primaryKey": false,
2066 + "notNull": false
2067 + },
2068 + "value_opportunity": {
2069 + "name": "value_opportunity",
2070 + "type": "real",
2071 + "primaryKey": false,
2072 + "notNull": false
2073 + },
2074 + "data_quality": {
2075 + "name": "data_quality",
2076 + "type": "real",
2077 + "primaryKey": false,
2078 + "notNull": false
2079 + },
2080 + "watchers": {
2081 + "name": "watchers",
2082 + "type": "integer",
2083 + "primaryKey": false,
2084 + "notNull": true,
2085 + "default": 0
2086 + },
2087 + "views_30d": {
2088 + "name": "views_30d",
2089 + "type": "integer",
2090 + "primaryKey": false,
2091 + "notNull": true,
2092 + "default": 0
2093 + },
2094 + "updated_at": {
2095 + "name": "updated_at",
2096 + "type": "timestamp with time zone",
2097 + "primaryKey": false,
2098 + "notNull": true,
2099 + "default": "now()"
2100 + }
2101 + },
2102 + "indexes": {
2103 + "asset_stats_riv_idx": {
2104 + "name": "asset_stats_riv_idx",
2105 + "columns": [
2106 + {
2107 + "expression": "riv_usd",
2108 + "isExpression": false,
2109 + "asc": true,
2110 + "nulls": "last"
2111 + }
2112 + ],
2113 + "isUnique": false,
2114 + "concurrently": false,
2115 + "method": "btree",
2116 + "with": {}
2117 + },
2118 + "asset_stats_trending_idx": {
2119 + "name": "asset_stats_trending_idx",
2120 + "columns": [
2121 + {
2122 + "expression": "trending_score",
2123 + "isExpression": false,
2124 + "asc": true,
2125 + "nulls": "last"
2126 + }
2127 + ],
2128 + "isUnique": false,
2129 + "concurrently": false,
2130 + "method": "btree",
2131 + "with": {}
2132 + },
2133 + "asset_stats_liquidity_idx": {
2134 + "name": "asset_stats_liquidity_idx",
2135 + "columns": [
2136 + {
2137 + "expression": "liquidity_score",
2138 + "isExpression": false,
2139 + "asc": true,
2140 + "nulls": "last"
2141 + }
2142 + ],
2143 + "isUnique": false,
2144 + "concurrently": false,
2145 + "method": "btree",
2146 + "with": {}
2147 + }
2148 + },
2149 + "foreignKeys": {},
2150 + "compositePrimaryKeys": {},
2151 + "uniqueConstraints": {},
2152 + "policies": {},
2153 + "checkConstraints": {},
2154 + "isRLSEnabled": false
2155 + },
2156 + "public.asset_variants": {
2157 + "name": "asset_variants",
2158 + "schema": "",
2159 + "columns": {
2160 + "id": {
2161 + "name": "id",
2162 + "type": "text",
2163 + "primaryKey": true,
2164 + "notNull": true
2165 + },
2166 + "asset_id": {
2167 + "name": "asset_id",
2168 + "type": "text",
2169 + "primaryKey": false,
2170 + "notNull": true
2171 + },
2172 + "variant_key": {
2173 + "name": "variant_key",
2174 + "type": "text",
2175 + "primaryKey": false,
2176 + "notNull": true
2177 + },
2178 + "grader": {
2179 + "name": "grader",
2180 + "type": "text",
2181 + "primaryKey": false,
2182 + "notNull": false
2183 + },
2184 + "grade": {
2185 + "name": "grade",
2186 + "type": "text",
2187 + "primaryKey": false,
2188 + "notNull": false
2189 + },
2190 + "qualifier": {
2191 + "name": "qualifier",
2192 + "type": "text",
2193 + "primaryKey": false,
2194 + "notNull": false
2195 + },
2196 + "condition": {
2197 + "name": "condition",
2198 + "type": "text",
2199 + "primaryKey": false,
2200 + "notNull": false
2201 + },
2202 + "completeness": {
2203 + "name": "completeness",
2204 + "type": "text",
2205 + "primaryKey": false,
2206 + "notNull": false
2207 + },
2208 + "size_label": {
2209 + "name": "size_label",
2210 + "type": "text",
2211 + "primaryKey": false,
2212 + "notNull": false
2213 + },
2214 + "label": {
2215 + "name": "label",
2216 + "type": "text",
2217 + "primaryKey": false,
2218 + "notNull": true
2219 + },
2220 + "is_default": {
2221 + "name": "is_default",
2222 + "type": "boolean",
2223 + "primaryKey": false,
2224 + "notNull": true,
2225 + "default": false
2226 + },
2227 + "created_at": {
2228 + "name": "created_at",
2229 + "type": "timestamp with time zone",
2230 + "primaryKey": false,
2231 + "notNull": true,
2232 + "default": "now()"
2233 + }
2234 + },
2235 + "indexes": {
2236 + "asset_variants_uq": {
2237 + "name": "asset_variants_uq",
2238 + "columns": [
2239 + {
2240 + "expression": "asset_id",
2241 + "isExpression": false,
2242 + "asc": true,
2243 + "nulls": "last"
2244 + },
2245 + {
2246 + "expression": "variant_key",
2247 + "isExpression": false,
2248 + "asc": true,
2249 + "nulls": "last"
2250 + }
2251 + ],
2252 + "isUnique": true,
2253 + "concurrently": false,
2254 + "method": "btree",
2255 + "with": {}
2256 + },
2257 + "asset_variants_asset_idx": {
2258 + "name": "asset_variants_asset_idx",
2259 + "columns": [
2260 + {
2261 + "expression": "asset_id",
2262 + "isExpression": false,
2263 + "asc": true,
2264 + "nulls": "last"
2265 + }
2266 + ],
2267 + "isUnique": false,
2268 + "concurrently": false,
2269 + "method": "btree",
2270 + "with": {}
2271 + }
2272 + },
2273 + "foreignKeys": {},
2274 + "compositePrimaryKeys": {},
2275 + "uniqueConstraints": {},
2276 + "policies": {},
2277 + "checkConstraints": {},
2278 + "isRLSEnabled": false
2279 + },
2280 + "public.assets": {
2281 + "name": "assets",
2282 + "schema": "",
2283 + "columns": {
2284 + "id": {
2285 + "name": "id",
2286 + "type": "text",
2287 + "primaryKey": true,
2288 + "notNull": true
2289 + },
2290 + "slug": {
2291 + "name": "slug",
2292 + "type": "text",
2293 + "primaryKey": false,
2294 + "notNull": true
2295 + },
2296 + "canonical_key": {
2297 + "name": "canonical_key",
2298 + "type": "text",
2299 + "primaryKey": false,
2300 + "notNull": true
2301 + },
2302 + "category_slug": {
2303 + "name": "category_slug",
2304 + "type": "text",
2305 + "primaryKey": false,
2306 + "notNull": true
2307 + },
2308 + "subcategory_slug": {
2309 + "name": "subcategory_slug",
2310 + "type": "text",
2311 + "primaryKey": false,
2312 + "notNull": false
2313 + },
2314 + "family_slug": {
2315 + "name": "family_slug",
2316 + "type": "text",
2317 + "primaryKey": false,
2318 + "notNull": true
2319 + },
2320 + "franchise": {
2321 + "name": "franchise",
2322 + "type": "text",
2323 + "primaryKey": false,
2324 + "notNull": false
2325 + },
2326 + "brand": {
2327 + "name": "brand",
2328 + "type": "text",
2329 + "primaryKey": false,
2330 + "notNull": false
2331 + },
2332 + "series": {
2333 + "name": "series",
2334 + "type": "text",
2335 + "primaryKey": false,
2336 + "notNull": false
2337 + },
2338 + "set_slug": {
2339 + "name": "set_slug",
2340 + "type": "text",
2341 + "primaryKey": false,
2342 + "notNull": false
2343 + },
2344 + "set_name": {
2345 + "name": "set_name",
2346 + "type": "text",
2347 + "primaryKey": false,
2348 + "notNull": false
2349 + },
2350 + "set_code": {
2351 + "name": "set_code",
2352 + "type": "text",
2353 + "primaryKey": false,
2354 + "notNull": false
2355 + },
2356 + "name": {
2357 + "name": "name",
2358 + "type": "text",
2359 + "primaryKey": false,
2360 + "notNull": true
2361 + },
2362 + "title": {
2363 + "name": "title",
2364 + "type": "text",
2365 + "primaryKey": false,
2366 + "notNull": true
2367 + },
2368 + "model": {
2369 + "name": "model",
2370 + "type": "text",
2371 + "primaryKey": false,
2372 + "notNull": false
2373 + },
2374 + "reference": {
2375 + "name": "reference",
2376 + "type": "text",
2377 + "primaryKey": false,
2378 + "notNull": false
2379 + },
2380 + "number": {
2381 + "name": "number",
2382 + "type": "text",
2383 + "primaryKey": false,
2384 + "notNull": false
2385 + },
2386 + "year": {
2387 + "name": "year",
2388 + "type": "integer",
2389 + "primaryKey": false,
2390 + "notNull": false
2391 + },
2392 + "edition": {
2393 + "name": "edition",
2394 + "type": "text",
2395 + "primaryKey": false,
2396 + "notNull": false
2397 + },
2398 + "variant": {
2399 + "name": "variant",
2400 + "type": "text",
2401 + "primaryKey": false,
2402 + "notNull": false
2403 + },
2404 + "language": {
2405 + "name": "language",
2406 + "type": "text",
2407 + "primaryKey": false,
2408 + "notNull": false
2409 + },
2410 + "region": {
2411 + "name": "region",
2412 + "type": "text",
2413 + "primaryKey": false,
2414 + "notNull": false
2415 + },
2416 + "country": {
2417 + "name": "country",
2418 + "type": "text",
2419 + "primaryKey": false,
2420 + "notNull": false
2421 + },
2422 + "material": {
2423 + "name": "material",
2424 + "type": "text",
2425 + "primaryKey": false,
2426 + "notNull": false
2427 + },
2428 + "size": {
2429 + "name": "size",
2430 + "type": "text",
2431 + "primaryKey": false,
2432 + "notNull": false
2433 + },
2434 + "color": {
2435 + "name": "color",
2436 + "type": "text",
2437 + "primaryKey": false,
2438 + "notNull": false
2439 + },
2440 + "rarity": {
2441 + "name": "rarity",
2442 + "type": "text",
2443 + "primaryKey": false,
2444 + "notNull": false
2445 + },
2446 + "production_quantity": {
2447 + "name": "production_quantity",
2448 + "type": "integer",
2449 + "primaryKey": false,
2450 + "notNull": false
2451 + },
2452 + "original_msrp": {
2453 + "name": "original_msrp",
2454 + "type": "numeric(18, 4)",
2455 + "primaryKey": false,
2456 + "notNull": false
2457 + },
2458 + "original_msrp_currency": {
2459 + "name": "original_msrp_currency",
2460 + "type": "text",
2461 + "primaryKey": false,
2462 + "notNull": false
2463 + },
2464 + "release_date": {
2465 + "name": "release_date",
2466 + "type": "text",
2467 + "primaryKey": false,
2468 + "notNull": false
2469 + },
2470 + "description": {
2471 + "name": "description",
2472 + "type": "text",
2473 + "primaryKey": false,
2474 + "notNull": false
2475 + },
2476 + "hero_image_url": {
2477 + "name": "hero_image_url",
2478 + "type": "text",
2479 + "primaryKey": false,
2480 + "notNull": false
2481 + },
2482 + "identifiers": {
2483 + "name": "identifiers",
2484 + "type": "jsonb",
2485 + "primaryKey": false,
2486 + "notNull": true,
2487 + "default": "'{}'::jsonb"
2488 + },
2489 + "metadata": {
2490 + "name": "metadata",
2491 + "type": "jsonb",
2492 + "primaryKey": false,
2493 + "notNull": true,
2494 + "default": "'{}'::jsonb"
2495 + },
2496 + "merged_from": {
2497 + "name": "merged_from",
2498 + "type": "text[]",
2499 + "primaryKey": false,
2500 + "notNull": true,
2501 + "default": "'{}'::text[]"
2502 + },
2503 + "data_quality": {
2504 + "name": "data_quality",
2505 + "type": "real",
2506 + "primaryKey": false,
2507 + "notNull": true,
2508 + "default": 0
2509 + },
2510 + "verified": {
2511 + "name": "verified",
2512 + "type": "boolean",
2513 + "primaryKey": false,
2514 + "notNull": true,
2515 + "default": false
2516 + },
2517 + "search": {
2518 + "name": "search",
2519 + "type": "tsvector",
2520 + "primaryKey": false,
2521 + "notNull": false,
2522 + "generated": {
2523 + "as": "setweight(to_tsvector('simple', coalesce(name, '')), 'A') || setweight(to_tsvector('simple', coalesce(set_name, '') || ' ' || coalesce(number, '') || ' ' || coalesce(variant, '') || ' ' || coalesce(edition, '')), 'B') || setweight(to_tsvector('simple', coalesce(brand, '') || ' ' || coalesce(franchise, '') || ' ' || coalesce(reference, '') || ' ' || coalesce(model, '') || ' ' || coalesce(year::text, '') || ' ' || coalesce(category_slug, '')), 'C')",
2524 + "type": "stored"
2525 + }
2526 + },
2527 + "created_at": {
2528 + "name": "created_at",
2529 + "type": "timestamp with time zone",
2530 + "primaryKey": false,
2531 + "notNull": true,
2532 + "default": "now()"
2533 + },
2534 + "updated_at": {
2535 + "name": "updated_at",
2536 + "type": "timestamp with time zone",
2537 + "primaryKey": false,
2538 + "notNull": true,
2539 + "default": "now()"
2540 + }
2541 + },
2542 + "indexes": {
2543 + "assets_canonical_key_uq": {
2544 + "name": "assets_canonical_key_uq",
2545 + "columns": [
2546 + {
2547 + "expression": "canonical_key",
2548 + "isExpression": false,
2549 + "asc": true,
2550 + "nulls": "last"
2551 + }
2552 + ],
2553 + "isUnique": true,
2554 + "concurrently": false,
2555 + "method": "btree",
2556 + "with": {}
2557 + },
2558 + "assets_slug_uq": {
2559 + "name": "assets_slug_uq",
2560 + "columns": [
2561 + {
2562 + "expression": "slug",
2563 + "isExpression": false,
2564 + "asc": true,
2565 + "nulls": "last"
2566 + }
2567 + ],
2568 + "isUnique": true,
2569 + "concurrently": false,
2570 + "method": "btree",
2571 + "with": {}
2572 + },
2573 + "assets_category_idx": {
2574 + "name": "assets_category_idx",
2575 + "columns": [
2576 + {
2577 + "expression": "category_slug",
2578 + "isExpression": false,
2579 + "asc": true,
2580 + "nulls": "last"
2581 + }
2582 + ],
2583 + "isUnique": false,
2584 + "concurrently": false,
2585 + "method": "btree",
2586 + "with": {}
2587 + },
2588 + "assets_family_idx": {
2589 + "name": "assets_family_idx",
2590 + "columns": [
2591 + {
2592 + "expression": "family_slug",
2593 + "isExpression": false,
2594 + "asc": true,
2595 + "nulls": "last"
2596 + }
2597 + ],
2598 + "isUnique": false,
2599 + "concurrently": false,
2600 + "method": "btree",
2601 + "with": {}
2602 + },
2603 + "assets_set_idx": {
2604 + "name": "assets_set_idx",
2605 + "columns": [
2606 + {
2607 + "expression": "set_slug",
2608 + "isExpression": false,
2609 + "asc": true,
2610 + "nulls": "last"
2611 + }
2612 + ],
2613 + "isUnique": false,
2614 + "concurrently": false,
2615 + "method": "btree",
2616 + "with": {}
2617 + },
2618 + "assets_search_gin": {
2619 + "name": "assets_search_gin",
2620 + "columns": [
2621 + {
2622 + "expression": "search",
2623 + "isExpression": false,
2624 + "asc": true,
2625 + "nulls": "last"
2626 + }
2627 + ],
2628 + "isUnique": false,
2629 + "concurrently": false,
2630 + "method": "gin",
2631 + "with": {}
2632 + },
2633 + "assets_title_trgm": {
2634 + "name": "assets_title_trgm",
2635 + "columns": [
2636 + {
2637 + "expression": "\"title\" gin_trgm_ops",
2638 + "asc": true,
2639 + "isExpression": true,
2640 + "nulls": "last"
2641 + }
2642 + ],
2643 + "isUnique": false,
2644 + "concurrently": false,
2645 + "method": "gin",
2646 + "with": {}
2647 + },
2648 + "assets_identifiers_gin": {
2649 + "name": "assets_identifiers_gin",
2650 + "columns": [
2651 + {
2652 + "expression": "identifiers",
2653 + "isExpression": false,
2654 + "asc": true,
2655 + "nulls": "last"
2656 + }
2657 + ],
2658 + "isUnique": false,
2659 + "concurrently": false,
2660 + "method": "gin",
2661 + "with": {}
2662 + }
2663 + },
2664 + "foreignKeys": {},
2665 + "compositePrimaryKeys": {},
2666 + "uniqueConstraints": {},
2667 + "policies": {},
2668 + "checkConstraints": {},
2669 + "isRLSEnabled": false
2670 + },
2671 + "public.grade_premiums": {
2672 + "name": "grade_premiums",
2673 + "schema": "",
2674 + "columns": {
2675 + "id": {
2676 + "name": "id",
2677 + "type": "text",
2678 + "primaryKey": true,
2679 + "notNull": true
2680 + },
2681 + "category_slug": {
2682 + "name": "category_slug",
2683 + "type": "text",
2684 + "primaryKey": false,
2685 + "notNull": true
2686 + },
2687 + "grader": {
2688 + "name": "grader",
2689 + "type": "text",
2690 + "primaryKey": false,
2691 + "notNull": true
2692 + },
2693 + "grade": {
2694 + "name": "grade",
2695 + "type": "text",
2696 + "primaryKey": false,
2697 + "notNull": true
2698 + },
2699 + "market_multiplier": {
2700 + "name": "market_multiplier",
2701 + "type": "real",
2702 + "primaryKey": false,
2703 + "notNull": true
2704 + },
2705 + "sample_size": {
2706 + "name": "sample_size",
2707 + "type": "integer",
2708 + "primaryKey": false,
2709 + "notNull": true
2710 + },
2711 + "computed_at": {
2712 + "name": "computed_at",
2713 + "type": "timestamp with time zone",
2714 + "primaryKey": false,
2715 + "notNull": true
2716 + }
2717 + },
2718 + "indexes": {
2719 + "grade_premiums_uq": {
2720 + "name": "grade_premiums_uq",
2721 + "columns": [
2722 + {
2723 + "expression": "category_slug",
2724 + "isExpression": false,
2725 + "asc": true,
2726 + "nulls": "last"
2727 + },
2728 + {
2729 + "expression": "grader",
2730 + "isExpression": false,
2731 + "asc": true,
2732 + "nulls": "last"
2733 + },
2734 + {
2735 + "expression": "grade",
2736 + "isExpression": false,
2737 + "asc": true,
2738 + "nulls": "last"
2739 + }
2740 + ],
2741 + "isUnique": true,
2742 + "concurrently": false,
2743 + "method": "btree",
2744 + "with": {}
2745 + }
2746 + },
2747 + "foreignKeys": {},
2748 + "compositePrimaryKeys": {},
2749 + "uniqueConstraints": {},
2750 + "policies": {},
2751 + "checkConstraints": {},
2752 + "isRLSEnabled": false
2753 + },
2754 + "public.images": {
2755 + "name": "images",
2756 + "schema": "",
2757 + "columns": {
2758 + "id": {
2759 + "name": "id",
2760 + "type": "text",
2761 + "primaryKey": true,
2762 + "notNull": true
2763 + },
2764 + "asset_id": {
2765 + "name": "asset_id",
2766 + "type": "text",
2767 + "primaryKey": false,
2768 + "notNull": false
2769 + },
2770 + "listing_id": {
2771 + "name": "listing_id",
2772 + "type": "text",
2773 + "primaryKey": false,
2774 + "notNull": false
2775 + },
2776 + "sale_id": {
2777 + "name": "sale_id",
2778 + "type": "text",
2779 + "primaryKey": false,
2780 + "notNull": false
2781 + },
2782 + "source_id": {
2783 + "name": "source_id",
2784 + "type": "text",
2785 + "primaryKey": false,
2786 + "notNull": false
2787 + },
2788 + "url": {
2789 + "name": "url",
2790 + "type": "text",
2791 + "primaryKey": false,
2792 + "notNull": true
2793 + },
2794 + "role": {
2795 + "name": "role",
2796 + "type": "text",
2797 + "primaryKey": false,
2798 + "notNull": true,
2799 + "default": "'gallery'"
2800 + },
2801 + "width": {
2802 + "name": "width",
2803 + "type": "integer",
2804 + "primaryKey": false,
2805 + "notNull": false
2806 + },
2807 + "height": {
2808 + "name": "height",
2809 + "type": "integer",
2810 + "primaryKey": false,
2811 + "notNull": false
2812 + },
2813 + "phash": {
2814 + "name": "phash",
2815 + "type": "text",
2816 + "primaryKey": false,
2817 + "notNull": false
2818 + },
2819 + "embedding": {
2820 + "name": "embedding",
2821 + "type": "vector(512)",
2822 + "primaryKey": false,
2823 + "notNull": false
2824 + },
2825 + "attribution": {
2826 + "name": "attribution",
2827 + "type": "text",
2828 + "primaryKey": false,
2829 + "notNull": false
2830 + },
2831 + "created_at": {
2832 + "name": "created_at",
2833 + "type": "timestamp with time zone",
2834 + "primaryKey": false,
2835 + "notNull": true,
2836 + "default": "now()"
2837 + }
2838 + },
2839 + "indexes": {
2840 + "images_asset_idx": {
2841 + "name": "images_asset_idx",
2842 + "columns": [
2843 + {
2844 + "expression": "asset_id",
2845 + "isExpression": false,
2846 + "asc": true,
2847 + "nulls": "last"
2848 + }
2849 + ],
2850 + "isUnique": false,
2851 + "concurrently": false,
2852 + "method": "btree",
2853 + "with": {}
2854 + },
2855 + "images_phash_idx": {
2856 + "name": "images_phash_idx",
2857 + "columns": [
2858 + {
2859 + "expression": "phash",
2860 + "isExpression": false,
2861 + "asc": true,
2862 + "nulls": "last"
2863 + }
2864 + ],
2865 + "isUnique": false,
2866 + "concurrently": false,
2867 + "method": "btree",
2868 + "with": {}
2869 + },
2870 + "images_url_uq": {
2871 + "name": "images_url_uq",
2872 + "columns": [
2873 + {
2874 + "expression": "url",
2875 + "isExpression": false,
2876 + "asc": true,
2877 + "nulls": "last"
2878 + }
2879 + ],
2880 + "isUnique": true,
2881 + "concurrently": false,
2882 + "method": "btree",
2883 + "with": {}
2884 + }
2885 + },
2886 + "foreignKeys": {},
2887 + "compositePrimaryKeys": {},
2888 + "uniqueConstraints": {},
2889 + "policies": {},
2890 + "checkConstraints": {},
2891 + "isRLSEnabled": false
2892 + },
2893 + "public.population_reports": {
2894 + "name": "population_reports",
2895 + "schema": "",
2896 + "columns": {
2897 + "id": {
2898 + "name": "id",
2899 + "type": "text",
2900 + "primaryKey": true,
2901 + "notNull": true
2902 + },
2903 + "asset_id": {
2904 + "name": "asset_id",
2905 + "type": "text",
2906 + "primaryKey": false,
2907 + "notNull": true
2908 + },
2909 + "grader": {
2910 + "name": "grader",
2911 + "type": "text",
2912 + "primaryKey": false,
2913 + "notNull": true
2914 + },
2915 + "source_id": {
2916 + "name": "source_id",
2917 + "type": "text",
2918 + "primaryKey": false,
2919 + "notNull": true
2920 + },
2921 + "source_url": {
2922 + "name": "source_url",
2923 + "type": "text",
2924 + "primaryKey": false,
2925 + "notNull": false
2926 + },
2927 + "report_date": {
2928 + "name": "report_date",
2929 + "type": "text",
2930 + "primaryKey": false,
2931 + "notNull": true
2932 + },
2933 + "total": {
2934 + "name": "total",
2935 + "type": "integer",
2936 + "primaryKey": false,
2937 + "notNull": true
2938 + },
2939 + "by_grade": {
2940 + "name": "by_grade",
2941 + "type": "jsonb",
2942 + "primaryKey": false,
2943 + "notNull": true
2944 + },
2945 + "created_at": {
2946 + "name": "created_at",
2947 + "type": "timestamp with time zone",
2948 + "primaryKey": false,
2949 + "notNull": true,
2950 + "default": "now()"
2951 + }
2952 + },
2953 + "indexes": {
2954 + "population_reports_uq": {
2955 + "name": "population_reports_uq",
2956 + "columns": [
2957 + {
2958 + "expression": "asset_id",
2959 + "isExpression": false,
2960 + "asc": true,
2961 + "nulls": "last"
2962 + },
2963 + {
2964 + "expression": "grader",
2965 + "isExpression": false,
2966 + "asc": true,
2967 + "nulls": "last"
2968 + },
2969 + {
2970 + "expression": "report_date",
2971 + "isExpression": false,
2972 + "asc": true,
2973 + "nulls": "last"
2974 + }
2975 + ],
2976 + "isUnique": true,
2977 + "concurrently": false,
2978 + "method": "btree",
2979 + "with": {}
2980 + }
2981 + },
2982 + "foreignKeys": {},
2983 + "compositePrimaryKeys": {},
2984 + "uniqueConstraints": {},
2985 + "policies": {},
2986 + "checkConstraints": {},
2987 + "isRLSEnabled": false
2988 + },
2989 + "public.variant_stats": {
2990 + "name": "variant_stats",
2991 + "schema": "",
2992 + "columns": {
2993 + "variant_id": {
2994 + "name": "variant_id",
2995 + "type": "text",
2996 + "primaryKey": true,
2997 + "notNull": true
2998 + },
2999 + "asset_id": {
3000 + "name": "asset_id",
3001 + "type": "text",
3002 + "primaryKey": false,
3003 + "notNull": true
3004 + },
3005 + "riv_usd": {
3006 + "name": "riv_usd",
3007 + "type": "numeric(18, 4)",
3008 + "primaryKey": false,
3009 + "notNull": false
3010 + },
3011 + "riv_low_usd": {
3012 + "name": "riv_low_usd",
3013 + "type": "numeric(18, 4)",
3014 + "primaryKey": false,
3015 + "notNull": false
3016 + },
3017 + "riv_high_usd": {
3018 + "name": "riv_high_usd",
3019 + "type": "numeric(18, 4)",
3020 + "primaryKey": false,
3021 + "notNull": false
3022 + },
3023 + "riv_confidence": {
3024 + "name": "riv_confidence",
3025 + "type": "numeric(8, 6)",
3026 + "primaryKey": false,
3027 + "notNull": false
3028 + },
3029 + "riv_sample_size": {
3030 + "name": "riv_sample_size",
3031 + "type": "integer",
3032 + "primaryKey": false,
3033 + "notNull": true,
3034 + "default": 0
3035 + },
3036 + "latest_sale_usd": {
3037 + "name": "latest_sale_usd",
3038 + "type": "numeric(18, 4)",
3039 + "primaryKey": false,
3040 + "notNull": false
3041 + },
3042 + "latest_sale_at": {
3043 + "name": "latest_sale_at",
3044 + "type": "timestamp with time zone",
3045 + "primaryKey": false,
3046 + "notNull": false
3047 + },
3048 + "change_30d": {
3049 + "name": "change_30d",
3050 + "type": "numeric(8, 6)",
3051 + "primaryKey": false,
3052 + "notNull": false
3053 + },
3054 + "change_1y": {
3055 + "name": "change_1y",
3056 + "type": "numeric(8, 6)",
3057 + "primaryKey": false,
3058 + "notNull": false
3059 + },
3060 + "sales_count": {
3061 + "name": "sales_count",
3062 + "type": "integer",
3063 + "primaryKey": false,
3064 + "notNull": true,
3065 + "default": 0
3066 + },
3067 + "sales_30d": {
3068 + "name": "sales_30d",
3069 + "type": "integer",
3070 + "primaryKey": false,
3071 + "notNull": true,
3072 + "default": 0
3073 + },
3074 + "active_listings": {
3075 + "name": "active_listings",
3076 + "type": "integer",
3077 + "primaryKey": false,
3078 + "notNull": true,
3079 + "default": 0
3080 + },
3081 + "min_ask_usd": {
3082 + "name": "min_ask_usd",
3083 + "type": "numeric(18, 4)",
3084 + "primaryKey": false,
3085 + "notNull": false
3086 + },
3087 + "liquidity_score": {
3088 + "name": "liquidity_score",
3089 + "type": "real",
3090 + "primaryKey": false,
3091 + "notNull": false
3092 + },
3093 + "updated_at": {
3094 + "name": "updated_at",
3095 + "type": "timestamp with time zone",
3096 + "primaryKey": false,
3097 + "notNull": true,
3098 + "default": "now()"
3099 + }
3100 + },
3101 + "indexes": {},
3102 + "foreignKeys": {},
3103 + "compositePrimaryKeys": {},
3104 + "uniqueConstraints": {},
3105 + "policies": {},
3106 + "checkConstraints": {},
3107 + "isRLSEnabled": false
3108 + },
3109 + "public.auction_lots": {
3110 + "name": "auction_lots",
3111 + "schema": "",
3112 + "columns": {
3113 + "id": {
3114 + "name": "id",
3115 + "type": "text",
3116 + "primaryKey": true,
3117 + "notNull": true
3118 + },
3119 + "auction_id": {
3120 + "name": "auction_id",
3121 + "type": "text",
3122 + "primaryKey": false,
3123 + "notNull": true
3124 + },
3125 + "asset_id": {
3126 + "name": "asset_id",
3127 + "type": "text",
3128 + "primaryKey": false,
3129 + "notNull": false
3130 + },
3131 + "variant_id": {
3132 + "name": "variant_id",
3133 + "type": "text",
3134 + "primaryKey": false,
3135 + "notNull": false
3136 + },
3137 + "source_id": {
3138 + "name": "source_id",
3139 + "type": "text",
3140 + "primaryKey": false,
3141 + "notNull": true
3142 + },
3143 + "lot_number": {
3144 + "name": "lot_number",
3145 + "type": "text",
3146 + "primaryKey": false,
3147 + "notNull": false
3148 + },
3149 + "title": {
3150 + "name": "title",
3151 + "type": "text",
3152 + "primaryKey": false,
3153 + "notNull": true
3154 + },
3155 + "url": {
3156 + "name": "url",
3157 + "type": "text",
3158 + "primaryKey": false,
3159 + "notNull": true
3160 + },
3161 + "estimate_low": {
3162 + "name": "estimate_low",
3163 + "type": "numeric(18, 4)",
3164 + "primaryKey": false,
3165 + "notNull": false
3166 + },
3167 + "estimate_high": {
3168 + "name": "estimate_high",
3169 + "type": "numeric(18, 4)",
3170 + "primaryKey": false,
3171 + "notNull": false
3172 + },
3173 + "current_bid": {
3174 + "name": "current_bid",
3175 + "type": "numeric(18, 4)",
3176 + "primaryKey": false,
3177 + "notNull": false
3178 + },
3179 + "hammer_price": {
3180 + "name": "hammer_price",
3181 + "type": "numeric(18, 4)",
3182 + "primaryKey": false,
3183 + "notNull": false
3184 + },
3185 + "currency": {
3186 + "name": "currency",
3187 + "type": "text",
3188 + "primaryKey": false,
3189 + "notNull": false
3190 + },
3191 + "bid_count": {
3192 + "name": "bid_count",
3193 + "type": "integer",
3194 + "primaryKey": false,
3195 + "notNull": false
3196 + },
3197 + "starts_at": {
3198 + "name": "starts_at",
3199 + "type": "timestamp with time zone",
3200 + "primaryKey": false,
3201 + "notNull": false
3202 + },
3203 + "ends_at": {
3204 + "name": "ends_at",
3205 + "type": "timestamp with time zone",
3206 + "primaryKey": false,
3207 + "notNull": false
3208 + },
3209 + "status": {
3210 + "name": "status",
3211 + "type": "text",
3212 + "primaryKey": false,
3213 + "notNull": true,
3214 + "default": "'upcoming'"
3215 + },
3216 + "image_urls": {
3217 + "name": "image_urls",
3218 + "type": "jsonb",
3219 + "primaryKey": false,
3220 + "notNull": true,
3221 + "default": "'[]'::jsonb"
3222 + },
3223 + "grader": {
3224 + "name": "grader",
3225 + "type": "text",
3226 + "primaryKey": false,
3227 + "notNull": false
3228 + },
3229 + "grade": {
3230 + "name": "grade",
3231 + "type": "text",
3232 + "primaryKey": false,
3233 + "notNull": false
3234 + },
3235 + "created_at": {
3236 + "name": "created_at",
3237 + "type": "timestamp with time zone",
3238 + "primaryKey": false,
3239 + "notNull": true,
3240 + "default": "now()"
3241 + },
3242 + "updated_at": {
3243 + "name": "updated_at",
3244 + "type": "timestamp with time zone",
3245 + "primaryKey": false,
3246 + "notNull": true,
3247 + "default": "now()"
3248 + }
3249 + },
3250 + "indexes": {
3251 + "auction_lots_url_uq": {
3252 + "name": "auction_lots_url_uq",
3253 + "columns": [
3254 + {
3255 + "expression": "url",
3256 + "isExpression": false,
3257 + "asc": true,
3258 + "nulls": "last"
3259 + }
3260 + ],
3261 + "isUnique": true,
3262 + "concurrently": false,
3263 + "method": "btree",
3264 + "with": {}
3265 + },
3266 + "auction_lots_auction_idx": {
3267 + "name": "auction_lots_auction_idx",
3268 + "columns": [
3269 + {
3270 + "expression": "auction_id",
3271 + "isExpression": false,
3272 + "asc": true,
3273 + "nulls": "last"
3274 + }
3275 + ],
3276 + "isUnique": false,
3277 + "concurrently": false,
3278 + "method": "btree",
3279 + "with": {}
3280 + },
3281 + "auction_lots_asset_idx": {
3282 + "name": "auction_lots_asset_idx",
3283 + "columns": [
3284 + {
3285 + "expression": "asset_id",
3286 + "isExpression": false,
3287 + "asc": true,
3288 + "nulls": "last"
3289 + }
3290 + ],
3291 + "isUnique": false,
3292 + "concurrently": false,
3293 + "method": "btree",
3294 + "with": {}
3295 + },
3296 + "auction_lots_ends_idx": {
3297 + "name": "auction_lots_ends_idx",
3298 + "columns": [
3299 + {
3300 + "expression": "ends_at",
3301 + "isExpression": false,
3302 + "asc": true,
3303 + "nulls": "last"
3304 + }
3305 + ],
3306 + "isUnique": false,
3307 + "concurrently": false,
3308 + "method": "btree",
3309 + "with": {}
3310 + }
3311 + },
3312 + "foreignKeys": {},
3313 + "compositePrimaryKeys": {},
3314 + "uniqueConstraints": {},
3315 + "policies": {},
3316 + "checkConstraints": {},
3317 + "isRLSEnabled": false
3318 + },
3319 + "public.auctions": {
3320 + "name": "auctions",
3321 + "schema": "",
3322 + "columns": {
3323 + "id": {
3324 + "name": "id",
3325 + "type": "text",
3326 + "primaryKey": true,
3327 + "notNull": true
3328 + },
3329 + "source_id": {
3330 + "name": "source_id",
3331 + "type": "text",
3332 + "primaryKey": false,
3333 + "notNull": true
3334 + },
3335 + "auction_house": {
3336 + "name": "auction_house",
3337 + "type": "text",
3338 + "primaryKey": false,
3339 + "notNull": true
3340 + },
3341 + "name": {
3342 + "name": "name",
3343 + "type": "text",
3344 + "primaryKey": false,
3345 + "notNull": true
3346 + },
3347 + "url": {
3348 + "name": "url",
3349 + "type": "text",
3350 + "primaryKey": false,
3351 + "notNull": true
3352 + },
3353 + "starts_at": {
3354 + "name": "starts_at",
3355 + "type": "timestamp with time zone",
3356 + "primaryKey": false,
3357 + "notNull": false
3358 + },
3359 + "ends_at": {
3360 + "name": "ends_at",
3361 + "type": "timestamp with time zone",
3362 + "primaryKey": false,
3363 + "notNull": false
3364 + },
3365 + "location": {
3366 + "name": "location",
3367 + "type": "text",
3368 + "primaryKey": false,
3369 + "notNull": false
3370 + },
3371 + "category_slugs": {
3372 + "name": "category_slugs",
3373 + "type": "text[]",
3374 + "primaryKey": false,
3375 + "notNull": true,
3376 + "default": "'{}'::text[]"
3377 + },
3378 + "lot_count": {
3379 + "name": "lot_count",
3380 + "type": "integer",
3381 + "primaryKey": false,
3382 + "notNull": false
3383 + },
3384 + "status": {
3385 + "name": "status",
3386 + "type": "text",
3387 + "primaryKey": false,
3388 + "notNull": true,
3389 + "default": "'upcoming'"
3390 + },
3391 + "currency": {
3392 + "name": "currency",
3393 + "type": "text",
3394 + "primaryKey": false,
3395 + "notNull": false
3396 + },
3397 + "created_at": {
3398 + "name": "created_at",
3399 + "type": "timestamp with time zone",
3400 + "primaryKey": false,
3401 + "notNull": true,
3402 + "default": "now()"
3403 + },
3404 + "updated_at": {
3405 + "name": "updated_at",
3406 + "type": "timestamp with time zone",
3407 + "primaryKey": false,
3408 + "notNull": true,
3409 + "default": "now()"
3410 + }
3411 + },
3412 + "indexes": {
3413 + "auctions_url_uq": {
3414 + "name": "auctions_url_uq",
3415 + "columns": [
3416 + {
3417 + "expression": "url",
3418 + "isExpression": false,
3419 + "asc": true,
3420 + "nulls": "last"
3421 + }
3422 + ],
3423 + "isUnique": true,
3424 + "concurrently": false,
3425 + "method": "btree",
3426 + "with": {}
3427 + },
3428 + "auctions_ends_idx": {
3429 + "name": "auctions_ends_idx",
3430 + "columns": [
3431 + {
3432 + "expression": "ends_at",
3433 + "isExpression": false,
3434 + "asc": true,
3435 + "nulls": "last"
3436 + }
3437 + ],
3438 + "isUnique": false,
3439 + "concurrently": false,
3440 + "method": "btree",
3441 + "with": {}
3442 + }
3443 + },
3444 + "foreignKeys": {},
3445 + "compositePrimaryKeys": {},
3446 + "uniqueConstraints": {},
3447 + "policies": {},
3448 + "checkConstraints": {},
3449 + "isRLSEnabled": false
3450 + },
3451 + "public.cross_listing_groups": {
3452 + "name": "cross_listing_groups",
3453 + "schema": "",
3454 + "columns": {
3455 + "id": {
3456 + "name": "id",
3457 + "type": "text",
3458 + "primaryKey": true,
3459 + "notNull": true
3460 + },
3461 + "asset_id": {
3462 + "name": "asset_id",
3463 + "type": "text",
3464 + "primaryKey": false,
3465 + "notNull": false
3466 + },
3467 + "signals": {
3468 + "name": "signals",
3469 + "type": "jsonb",
3470 + "primaryKey": false,
3471 + "notNull": true,
3472 + "default": "'{}'::jsonb"
3473 + },
3474 + "created_at": {
3475 + "name": "created_at",
3476 + "type": "timestamp with time zone",
3477 + "primaryKey": false,
3478 + "notNull": true,
3479 + "default": "now()"
3480 + }
3481 + },
3482 + "indexes": {},
3483 + "foreignKeys": {},
3484 + "compositePrimaryKeys": {},
3485 + "uniqueConstraints": {},
3486 + "policies": {},
3487 + "checkConstraints": {},
3488 + "isRLSEnabled": false
3489 + },
3490 + "public.fx_rates": {
3491 + "name": "fx_rates",
3492 + "schema": "",
3493 + "columns": {
3494 + "date": {
3495 + "name": "date",
3496 + "type": "date",
3497 + "primaryKey": false,
3498 + "notNull": true
3499 + },
3500 + "base": {
3501 + "name": "base",
3502 + "type": "text",
3503 + "primaryKey": false,
3504 + "notNull": true
3505 + },
3506 + "quote": {
3507 + "name": "quote",
3508 + "type": "text",
3509 + "primaryKey": false,
3510 + "notNull": true
3511 + },
3512 + "rate": {
3513 + "name": "rate",
3514 + "type": "real",
3515 + "primaryKey": false,
3516 + "notNull": true
3517 + },
3518 + "source": {
3519 + "name": "source",
3520 + "type": "text",
3521 + "primaryKey": false,
3522 + "notNull": true,
3523 + "default": "'ecb'"
3524 + }
3525 + },
3526 + "indexes": {
3527 + "fx_rates_uq": {
3528 + "name": "fx_rates_uq",
3529 + "columns": [
3530 + {
3531 + "expression": "date",
3532 + "isExpression": false,
3533 + "asc": true,
3534 + "nulls": "last"
3535 + },
3536 + {
3537 + "expression": "base",
3538 + "isExpression": false,
3539 + "asc": true,
3540 + "nulls": "last"
3541 + },
3542 + {
3543 + "expression": "quote",
3544 + "isExpression": false,
3545 + "asc": true,
3546 + "nulls": "last"
3547 + }
3548 + ],
3549 + "isUnique": true,
3550 + "concurrently": false,
3551 + "method": "btree",
3552 + "with": {}
3553 + }
3554 + },
3555 + "foreignKeys": {},
3556 + "compositePrimaryKeys": {},
3557 + "uniqueConstraints": {},
3558 + "policies": {},
3559 + "checkConstraints": {},
3560 + "isRLSEnabled": false
3561 + },
3562 + "public.listing_events": {
3563 + "name": "listing_events",
3564 + "schema": "",
3565 + "columns": {
3566 + "id": {
3567 + "name": "id",
3568 + "type": "text",
3569 + "primaryKey": true,
3570 + "notNull": true
3571 + },
3572 + "listing_id": {
3573 + "name": "listing_id",
3574 + "type": "text",
3575 + "primaryKey": false,
3576 + "notNull": true
3577 + },
3578 + "event_type": {
3579 + "name": "event_type",
3580 + "type": "text",
3581 + "primaryKey": false,
3582 + "notNull": true
3583 + },
3584 + "old_price": {
3585 + "name": "old_price",
3586 + "type": "numeric(18, 4)",
3587 + "primaryKey": false,
3588 + "notNull": false
3589 + },
3590 + "new_price": {
3591 + "name": "new_price",
3592 + "type": "numeric(18, 4)",
3593 + "primaryKey": false,
3594 + "notNull": false
3595 + },
3596 + "currency": {
3597 + "name": "currency",
3598 + "type": "text",
3599 + "primaryKey": false,
3600 + "notNull": false
3601 + },
3602 + "occurred_at": {
3603 + "name": "occurred_at",
3604 + "type": "timestamp with time zone",
3605 + "primaryKey": false,
3606 + "notNull": true
3607 + }
3608 + },
3609 + "indexes": {
3610 + "listing_events_listing_idx": {
3611 + "name": "listing_events_listing_idx",
3612 + "columns": [
3613 + {
3614 + "expression": "listing_id",
3615 + "isExpression": false,
3616 + "asc": true,
3617 + "nulls": "last"
3618 + },
3619 + {
3620 + "expression": "occurred_at",
3621 + "isExpression": false,
3622 + "asc": true,
3623 + "nulls": "last"
3624 + }
3625 + ],
3626 + "isUnique": false,
3627 + "concurrently": false,
3628 + "method": "btree",
3629 + "with": {}
3630 + }
3631 + },
3632 + "foreignKeys": {},
3633 + "compositePrimaryKeys": {},
3634 + "uniqueConstraints": {},
3635 + "policies": {},
3636 + "checkConstraints": {},
3637 + "isRLSEnabled": false
3638 + },
3639 + "public.listings": {
3640 + "name": "listings",
3641 + "schema": "",
3642 + "columns": {
3643 + "id": {
3644 + "name": "id",
3645 + "type": "text",
3646 + "primaryKey": true,
3647 + "notNull": true
3648 + },
3649 + "asset_id": {
3650 + "name": "asset_id",
3651 + "type": "text",
3652 + "primaryKey": false,
3653 + "notNull": true
3654 + },
3655 + "variant_id": {
3656 + "name": "variant_id",
3657 + "type": "text",
3658 + "primaryKey": false,
3659 + "notNull": false
3660 + },
3661 + "source_id": {
3662 + "name": "source_id",
3663 + "type": "text",
3664 + "primaryKey": false,
3665 + "notNull": true
3666 + },
3667 + "connector_id": {
3668 + "name": "connector_id",
3669 + "type": "text",
3670 + "primaryKey": false,
3671 + "notNull": true
3672 + },
3673 + "raw_record_id": {
3674 + "name": "raw_record_id",
3675 + "type": "text",
3676 + "primaryKey": false,
3677 + "notNull": false
3678 + },
3679 + "source_url": {
3680 + "name": "source_url",
3681 + "type": "text",
3682 + "primaryKey": false,
3683 + "notNull": true
3684 + },
3685 + "external_id": {
3686 + "name": "external_id",
3687 + "type": "text",
3688 + "primaryKey": false,
3689 + "notNull": true
3690 + },
3691 + "listing_type": {
3692 + "name": "listing_type",
3693 + "type": "text",
3694 + "primaryKey": false,
3695 + "notNull": true,
3696 + "default": "'unknown'"
3697 + },
3698 + "price": {
3699 + "name": "price",
3700 + "type": "numeric(18, 4)",
3701 + "primaryKey": false,
3702 + "notNull": false
3703 + },
3704 + "currency": {
3705 + "name": "currency",
3706 + "type": "text",
3707 + "primaryKey": false,
3708 + "notNull": false
3709 + },
3710 + "price_usd": {
3711 + "name": "price_usd",
3712 + "type": "numeric(18, 4)",
3713 + "primaryKey": false,
3714 + "notNull": false
3715 + },
3716 + "seller": {
3717 + "name": "seller",
3718 + "type": "text",
3719 + "primaryKey": false,
3720 + "notNull": false
3721 + },
3722 + "seller_reputation": {
3723 + "name": "seller_reputation",
3724 + "type": "text",
3725 + "primaryKey": false,
3726 + "notNull": false
3727 + },
3728 + "location": {
3729 + "name": "location",
3730 + "type": "text",
3731 + "primaryKey": false,
3732 + "notNull": false
3733 + },
3734 + "shipping_cost": {
3735 + "name": "shipping_cost",
3736 + "type": "numeric(18, 4)",
3737 + "primaryKey": false,
3738 + "notNull": false
3739 + },
3740 + "quantity": {
3741 + "name": "quantity",
3742 + "type": "integer",
3743 + "primaryKey": false,
3744 + "notNull": false
3745 + },
3746 + "condition": {
3747 + "name": "condition",
3748 + "type": "text",
3749 + "primaryKey": false,
3750 + "notNull": false
3751 + },
3752 + "grader": {
3753 + "name": "grader",
3754 + "type": "text",
3755 + "primaryKey": false,
3756 + "notNull": false
3757 + },
3758 + "grade": {
3759 + "name": "grade",
3760 + "type": "text",
3761 + "primaryKey": false,
3762 + "notNull": false
3763 + },
3764 + "certification_number": {
3765 + "name": "certification_number",
3766 + "type": "text",
3767 + "primaryKey": false,
3768 + "notNull": false
3769 + },
3770 + "image_urls": {
3771 + "name": "image_urls",
3772 + "type": "jsonb",
3773 + "primaryKey": false,
3774 + "notNull": true,
3775 + "default": "'[]'::jsonb"
3776 + },
3777 + "raw_title": {
3778 + "name": "raw_title",
3779 + "type": "text",
3780 + "primaryKey": false,
3781 + "notNull": true
3782 + },
3783 + "description": {
3784 + "name": "description",
3785 + "type": "text",
3786 + "primaryKey": false,
3787 + "notNull": false
3788 + },
3789 + "listed_at": {
3790 + "name": "listed_at",
3791 + "type": "timestamp with time zone",
3792 + "primaryKey": false,
3793 + "notNull": false
3794 + },
3795 + "ends_at": {
3796 + "name": "ends_at",
3797 + "type": "timestamp with time zone",
3798 + "primaryKey": false,
3799 + "notNull": false
3800 + },
3801 + "availability": {
3802 + "name": "availability",
3803 + "type": "text",
3804 + "primaryKey": false,
3805 + "notNull": true,
3806 + "default": "'available'"
3807 + },
3808 + "bid_count": {
3809 + "name": "bid_count",
3810 + "type": "integer",
3811 + "primaryKey": false,
3812 + "notNull": false
3813 + },
3814 + "first_seen_at": {
3815 + "name": "first_seen_at",
3816 + "type": "timestamp with time zone",
3817 + "primaryKey": false,
3818 + "notNull": true
3819 + },
3820 + "last_seen_at": {
3821 + "name": "last_seen_at",
3822 + "type": "timestamp with time zone",
3823 + "primaryKey": false,
3824 + "notNull": true
3825 + },
3826 + "price_changed_at": {
3827 + "name": "price_changed_at",
3828 + "type": "timestamp with time zone",
3829 + "primaryKey": false,
3830 + "notNull": false
3831 + },
3832 + "cross_listing_group_id": {
3833 + "name": "cross_listing_group_id",
3834 + "type": "text",
3835 + "primaryKey": false,
3836 + "notNull": false
3837 + },
3838 + "confidence": {
3839 + "name": "confidence",
3840 + "type": "numeric(8, 6)",
3841 + "primaryKey": false,
3842 + "notNull": true,
3843 + "default": 0.8
3844 + },
3845 + "data_quality": {
3846 + "name": "data_quality",
3847 + "type": "real",
3848 + "primaryKey": false,
3849 + "notNull": true,
3850 + "default": 0
3851 + },
3852 + "flags": {
3853 + "name": "flags",
3854 + "type": "text[]",
3855 + "primaryKey": false,
3856 + "notNull": true,
3857 + "default": "'{}'::text[]"
3858 + },
3859 + "discount_to_riv": {
3860 + "name": "discount_to_riv",
3861 + "type": "numeric(8, 6)",
3862 + "primaryKey": false,
3863 + "notNull": false
3864 + },
3865 + "created_at": {
3866 + "name": "created_at",
3867 + "type": "timestamp with time zone",
3868 + "primaryKey": false,
3869 + "notNull": true,
3870 + "default": "now()"
3871 + },
3872 + "updated_at": {
3873 + "name": "updated_at",
3874 + "type": "timestamp with time zone",
3875 + "primaryKey": false,
3876 + "notNull": true,
3877 + "default": "now()"
3878 + }
3879 + },
3880 + "indexes": {
3881 + "listings_source_external_uq": {
3882 + "name": "listings_source_external_uq",
3883 + "columns": [
3884 + {
3885 + "expression": "source_id",
3886 + "isExpression": false,
3887 + "asc": true,
3888 + "nulls": "last"
3889 + },
3890 + {
3891 + "expression": "external_id",
3892 + "isExpression": false,
3893 + "asc": true,
3894 + "nulls": "last"
3895 + }
3896 + ],
3897 + "isUnique": true,
3898 + "concurrently": false,
3899 + "method": "btree",
3900 + "with": {}
3901 + },
3902 + "listings_asset_avail_idx": {
3903 + "name": "listings_asset_avail_idx",
3904 + "columns": [
3905 + {
3906 + "expression": "asset_id",
3907 + "isExpression": false,
3908 + "asc": true,
3909 + "nulls": "last"
3910 + },
3911 + {
3912 + "expression": "availability",
3913 + "isExpression": false,
3914 + "asc": true,
3915 + "nulls": "last"
3916 + }
3917 + ],
3918 + "isUnique": false,
3919 + "concurrently": false,
3920 + "method": "btree",
3921 + "with": {}
3922 + },
3923 + "listings_avail_price_idx": {
3924 + "name": "listings_avail_price_idx",
3925 + "columns": [
3926 + {
3927 + "expression": "availability",
3928 + "isExpression": false,
3929 + "asc": true,
3930 + "nulls": "last"
3931 + },
3932 + {
3933 + "expression": "price_usd",
3934 + "isExpression": false,
3935 + "asc": true,
3936 + "nulls": "last"
3937 + }
3938 + ],
3939 + "isUnique": false,
3940 + "concurrently": false,
3941 + "method": "btree",
3942 + "with": {}
3943 + },
3944 + "listings_ends_idx": {
3945 + "name": "listings_ends_idx",
3946 + "columns": [
3947 + {
3948 + "expression": "ends_at",
3949 + "isExpression": false,
3950 + "asc": true,
3951 + "nulls": "last"
3952 + }
3953 + ],
3954 + "isUnique": false,
3955 + "concurrently": false,
3956 + "method": "btree",
3957 + "with": {}
3958 + },
3959 + "listings_last_seen_idx": {
3960 + "name": "listings_last_seen_idx",
3961 + "columns": [
3962 + {
3963 + "expression": "last_seen_at",
3964 + "isExpression": false,
3965 + "asc": true,
3966 + "nulls": "last"
3967 + }
3968 + ],
3969 + "isUnique": false,
3970 + "concurrently": false,
3971 + "method": "btree",
3972 + "with": {}
3973 + }
3974 + },
3975 + "foreignKeys": {},
3976 + "compositePrimaryKeys": {},
3977 + "uniqueConstraints": {},
3978 + "policies": {},
3979 + "checkConstraints": {},
3980 + "isRLSEnabled": false
3981 + },
3982 + "public.news": {
3983 + "name": "news",
3984 + "schema": "",
3985 + "columns": {
3986 + "id": {
3987 + "name": "id",
3988 + "type": "text",
3989 + "primaryKey": true,
3990 + "notNull": true
3991 + },
3992 + "source_id": {
3993 + "name": "source_id",
3994 + "type": "text",
3995 + "primaryKey": false,
3996 + "notNull": true
3997 + },
3998 + "url": {
3999 + "name": "url",
4000 + "type": "text",
4001 + "primaryKey": false,
4002 + "notNull": true
4003 + },
4004 + "title": {
4005 + "name": "title",
4006 + "type": "text",
4007 + "primaryKey": false,
4008 + "notNull": true
4009 + },
4010 + "summary": {
4011 + "name": "summary",
4012 + "type": "text",
4013 + "primaryKey": false,
4014 + "notNull": false
4015 + },
4016 + "ai_summary": {
4017 + "name": "ai_summary",
4018 + "type": "text",
4019 + "primaryKey": false,
4020 + "notNull": false
4021 + },
4022 + "published_at": {
4023 + "name": "published_at",
4024 + "type": "timestamp with time zone",
4025 + "primaryKey": false,
4026 + "notNull": false
4027 + },
4028 + "category_slugs": {
4029 + "name": "category_slugs",
4030 + "type": "text[]",
4031 + "primaryKey": false,
4032 + "notNull": true,
4033 + "default": "'{}'::text[]"
4034 + },
4035 + "news_type": {
4036 + "name": "news_type",
4037 + "type": "text",
4038 + "primaryKey": false,
4039 + "notNull": false
4040 + },
4041 + "image_url": {
4042 + "name": "image_url",
4043 + "type": "text",
4044 + "primaryKey": false,
4045 + "notNull": false
4046 + },
4047 + "fetched_at": {
4048 + "name": "fetched_at",
4049 + "type": "timestamp with time zone",
4050 + "primaryKey": false,
4051 + "notNull": true
4052 + }
4053 + },
4054 + "indexes": {
4055 + "news_url_uq": {
4056 + "name": "news_url_uq",
4057 + "columns": [
4058 + {
4059 + "expression": "url",
4060 + "isExpression": false,
4061 + "asc": true,
4062 + "nulls": "last"
4063 + }
4064 + ],
4065 + "isUnique": true,
4066 + "concurrently": false,
4067 + "method": "btree",
4068 + "with": {}
4069 + },
4070 + "news_published_idx": {
4071 + "name": "news_published_idx",
4072 + "columns": [
4073 + {
4074 + "expression": "published_at",
4075 + "isExpression": false,
4076 + "asc": true,
4077 + "nulls": "last"
4078 + }
4079 + ],
4080 + "isUnique": false,
4081 + "concurrently": false,
4082 + "method": "btree",
4083 + "with": {}
4084 + }
4085 + },
4086 + "foreignKeys": {},
4087 + "compositePrimaryKeys": {},
4088 + "uniqueConstraints": {},
4089 + "policies": {},
4090 + "checkConstraints": {},
4091 + "isRLSEnabled": false
4092 + },
4093 + "public.price_observations": {
4094 + "name": "price_observations",
4095 + "schema": "",
4096 + "columns": {
4097 + "id": {
4098 + "name": "id",
4099 + "type": "text",
4100 + "primaryKey": true,
4101 + "notNull": true
4102 + },
4103 + "asset_id": {
4104 + "name": "asset_id",
4105 + "type": "text",
4106 + "primaryKey": false,
4107 + "notNull": true
4108 + },
4109 + "variant_id": {
4110 + "name": "variant_id",
4111 + "type": "text",
4112 + "primaryKey": false,
4113 + "notNull": false
4114 + },
4115 + "source_id": {
4116 + "name": "source_id",
4117 + "type": "text",
4118 + "primaryKey": false,
4119 + "notNull": true
4120 + },
4121 + "connector_id": {
4122 + "name": "connector_id",
4123 + "type": "text",
4124 + "primaryKey": false,
4125 + "notNull": true
4126 + },
4127 + "raw_record_id": {
4128 + "name": "raw_record_id",
4129 + "type": "text",
4130 + "primaryKey": false,
4131 + "notNull": false
4132 + },
4133 + "source_url": {
4134 + "name": "source_url",
4135 + "type": "text",
4136 + "primaryKey": false,
4137 + "notNull": true
4138 + },
4139 + "price_kind": {
4140 + "name": "price_kind",
4141 + "type": "text",
4142 + "primaryKey": false,
4143 + "notNull": true
4144 + },
4145 + "price": {
4146 + "name": "price",
4147 + "type": "numeric(18, 4)",
4148 + "primaryKey": false,
4149 + "notNull": true
4150 + },
4151 + "currency": {
4152 + "name": "currency",
4153 + "type": "text",
4154 + "primaryKey": false,
4155 + "notNull": true
4156 + },
4157 + "price_usd": {
4158 + "name": "price_usd",
4159 + "type": "numeric(18, 4)",
4160 + "primaryKey": false,
4161 + "notNull": true
4162 + },
4163 + "observation_date": {
4164 + "name": "observation_date",
4165 + "type": "date",
4166 + "primaryKey": false,
4167 + "notNull": true
4168 + },
4169 + "sample_size": {
4170 + "name": "sample_size",
4171 + "type": "integer",
4172 + "primaryKey": false,
4173 + "notNull": false
4174 + },
4175 + "dedupe_key": {
4176 + "name": "dedupe_key",
4177 + "type": "text",
4178 + "primaryKey": false,
4179 + "notNull": true
4180 + },
4181 + "created_at": {
4182 + "name": "created_at",
4183 + "type": "timestamp with time zone",
4184 + "primaryKey": false,
4185 + "notNull": true,
4186 + "default": "now()"
4187 + }
4188 + },
4189 + "indexes": {
4190 + "price_observations_dedupe_uq": {
4191 + "name": "price_observations_dedupe_uq",
4192 + "columns": [
4193 + {
4194 + "expression": "dedupe_key",
4195 + "isExpression": false,
4196 + "asc": true,
4197 + "nulls": "last"
4198 + }
4199 + ],
4200 + "isUnique": true,
4201 + "concurrently": false,
4202 + "method": "btree",
4203 + "with": {}
4204 + },
4205 + "price_observations_asset_date_idx": {
4206 + "name": "price_observations_asset_date_idx",
4207 + "columns": [
4208 + {
4209 + "expression": "asset_id",
4210 + "isExpression": false,
4211 + "asc": true,
4212 + "nulls": "last"
4213 + },
4214 + {
4215 + "expression": "observation_date",
4216 + "isExpression": false,
4217 + "asc": true,
4218 + "nulls": "last"
4219 + }
4220 + ],
4221 + "isUnique": false,
4222 + "concurrently": false,
4223 + "method": "btree",
4224 + "with": {}
4225 + }
4226 + },
4227 + "foreignKeys": {},
4228 + "compositePrimaryKeys": {},
4229 + "uniqueConstraints": {},
4230 + "policies": {},
4231 + "checkConstraints": {},
4232 + "isRLSEnabled": false
4233 + },
4234 + "public.sales": {
4235 + "name": "sales",
4236 + "schema": "",
4237 + "columns": {
4238 + "id": {
4239 + "name": "id",
4240 + "type": "text",
4241 + "primaryKey": true,
4242 + "notNull": true
4243 + },
4244 + "asset_id": {
4245 + "name": "asset_id",
4246 + "type": "text",
4247 + "primaryKey": false,
4248 + "notNull": true
4249 + },
4250 + "variant_id": {
4251 + "name": "variant_id",
4252 + "type": "text",
4253 + "primaryKey": false,
4254 + "notNull": false
4255 + },
4256 + "source_id": {
4257 + "name": "source_id",
4258 + "type": "text",
4259 + "primaryKey": false,
4260 + "notNull": true
4261 + },
4262 + "connector_id": {
4263 + "name": "connector_id",
4264 + "type": "text",
4265 + "primaryKey": false,
4266 + "notNull": true
4267 + },
4268 + "raw_record_id": {
4269 + "name": "raw_record_id",
4270 + "type": "text",
4271 + "primaryKey": false,
4272 + "notNull": false
4273 + },
4274 + "normalized_record_id": {
4275 + "name": "normalized_record_id",
4276 + "type": "text",
4277 + "primaryKey": false,
4278 + "notNull": false
4279 + },
4280 + "source_url": {
4281 + "name": "source_url",
4282 + "type": "text",
4283 + "primaryKey": false,
4284 + "notNull": true
4285 + },
4286 + "external_id": {
4287 + "name": "external_id",
4288 + "type": "text",
4289 + "primaryKey": false,
4290 + "notNull": false
4291 + },
4292 + "sale_type": {
4293 + "name": "sale_type",
4294 + "type": "text",
4295 + "primaryKey": false,
4296 + "notNull": true,
4297 + "default": "'unknown'"
4298 + },
4299 + "sale_date": {
4300 + "name": "sale_date",
4301 + "type": "timestamp with time zone",
4302 + "primaryKey": false,
4303 + "notNull": true
4304 + },
4305 + "price": {
4306 + "name": "price",
4307 + "type": "numeric(18, 4)",
4308 + "primaryKey": false,
4309 + "notNull": true
4310 + },
4311 + "currency": {
4312 + "name": "currency",
4313 + "type": "text",
4314 + "primaryKey": false,
4315 + "notNull": true
4316 + },
4317 + "price_usd": {
4318 + "name": "price_usd",
4319 + "type": "numeric(18, 4)",
4320 + "primaryKey": false,
4321 + "notNull": true
4322 + },
4323 + "fx_rate": {
4324 + "name": "fx_rate",
4325 + "type": "real",
4326 + "primaryKey": false,
4327 + "notNull": false
4328 + },
4329 + "fx_date": {
4330 + "name": "fx_date",
4331 + "type": "date",
4332 + "primaryKey": false,
4333 + "notNull": false
4334 + },
4335 + "buyer_premium_included": {
4336 + "name": "buyer_premium_included",
4337 + "type": "boolean",
4338 + "primaryKey": false,
4339 + "notNull": false
4340 + },
4341 + "quantity": {
4342 + "name": "quantity",
4343 + "type": "integer",
4344 + "primaryKey": false,
4345 + "notNull": true,
4346 + "default": 1
4347 + },
4348 + "is_bundle": {
4349 + "name": "is_bundle",
4350 + "type": "boolean",
4351 + "primaryKey": false,
4352 + "notNull": true,
4353 + "default": false
4354 + },
4355 + "condition": {
4356 + "name": "condition",
4357 + "type": "text",
4358 + "primaryKey": false,
4359 + "notNull": false
4360 + },
4361 + "grader": {
4362 + "name": "grader",
4363 + "type": "text",
4364 + "primaryKey": false,
4365 + "notNull": false
4366 + },
4367 + "grade": {
4368 + "name": "grade",
4369 + "type": "text",
4370 + "primaryKey": false,
4371 + "notNull": false
4372 + },
4373 + "certification_number": {
4374 + "name": "certification_number",
4375 + "type": "text",
4376 + "primaryKey": false,
4377 + "notNull": false
4378 + },
4379 + "location": {
4380 + "name": "location",
4381 + "type": "text",
4382 + "primaryKey": false,
4383 + "notNull": false
4384 + },
4385 + "auction_house": {
4386 + "name": "auction_house",
4387 + "type": "text",
4388 + "primaryKey": false,
4389 + "notNull": false
4390 + },
4391 + "lot_number": {
4392 + "name": "lot_number",
4393 + "type": "text",
4394 + "primaryKey": false,
4395 + "notNull": false
4396 + },
4397 + "image_urls": {
4398 + "name": "image_urls",
4399 + "type": "jsonb",
4400 + "primaryKey": false,
4401 + "notNull": true,
4402 + "default": "'[]'::jsonb"
4403 + },
4404 + "raw_title": {
4405 + "name": "raw_title",
4406 + "type": "text",
4407 + "primaryKey": false,
4408 + "notNull": true
4409 + },
4410 + "confidence": {
4411 + "name": "confidence",
4412 + "type": "numeric(8, 6)",
4413 + "primaryKey": false,
4414 + "notNull": true,
4415 + "default": 0.8
4416 + },
4417 + "data_quality": {
4418 + "name": "data_quality",
4419 + "type": "real",
4420 + "primaryKey": false,
4421 + "notNull": true,
4422 + "default": 0
4423 + },
4424 + "status": {
4425 + "name": "status",
4426 + "type": "text",
4427 + "primaryKey": false,
4428 + "notNull": true,
4429 + "default": "'valid'"
4430 + },
4431 + "flags": {
4432 + "name": "flags",
4433 + "type": "text[]",
4434 + "primaryKey": false,
4435 + "notNull": true,
4436 + "default": "'{}'::text[]"
4437 + },
4438 + "dedupe_key": {
4439 + "name": "dedupe_key",
4440 + "type": "text",
4441 + "primaryKey": false,
4442 + "notNull": true
4443 + },
4444 + "created_at": {
4445 + "name": "created_at",
4446 + "type": "timestamp with time zone",
4447 + "primaryKey": false,
4448 + "notNull": true,
4449 + "default": "now()"
4450 + }
4451 + },
4452 + "indexes": {
4453 + "sales_dedupe_uq": {
4454 + "name": "sales_dedupe_uq",
4455 + "columns": [
4456 + {
4457 + "expression": "dedupe_key",
4458 + "isExpression": false,
4459 + "asc": true,
4460 + "nulls": "last"
4461 + }
4462 + ],
4463 + "isUnique": true,
4464 + "concurrently": false,
4465 + "method": "btree",
4466 + "with": {}
4467 + },
4468 + "sales_asset_date_idx": {
4469 + "name": "sales_asset_date_idx",
4470 + "columns": [
4471 + {
4472 + "expression": "asset_id",
4473 + "isExpression": false,
4474 + "asc": true,
4475 + "nulls": "last"
4476 + },
4477 + {
4478 + "expression": "sale_date",
4479 + "isExpression": false,
4480 + "asc": true,
4481 + "nulls": "last"
4482 + }
4483 + ],
4484 + "isUnique": false,
4485 + "concurrently": false,
4486 + "method": "btree",
4487 + "with": {}
4488 + },
4489 + "sales_variant_date_idx": {
4490 + "name": "sales_variant_date_idx",
4491 + "columns": [
4492 + {
4493 + "expression": "variant_id",
4494 + "isExpression": false,
4495 + "asc": true,
4496 + "nulls": "last"
4497 + },
4498 + {
4499 + "expression": "sale_date",
4500 + "isExpression": false,
4501 + "asc": true,
4502 + "nulls": "last"
4503 + }
4504 + ],
4505 + "isUnique": false,
4506 + "concurrently": false,
4507 + "method": "btree",
4508 + "with": {}
4509 + },
4510 + "sales_source_idx": {
4511 + "name": "sales_source_idx",
4512 + "columns": [
4513 + {
4514 + "expression": "source_id",
4515 + "isExpression": false,
4516 + "asc": true,
4517 + "nulls": "last"
4518 + },
4519 + {
4520 + "expression": "sale_date",
4521 + "isExpression": false,
4522 + "asc": true,
4523 + "nulls": "last"
4524 + }
4525 + ],
4526 + "isUnique": false,
4527 + "concurrently": false,
4528 + "method": "btree",
4529 + "with": {}
4530 + },
4531 + "sales_date_idx": {
4532 + "name": "sales_date_idx",
4533 + "columns": [
4534 + {
4535 + "expression": "sale_date",
4536 + "isExpression": false,
4537 + "asc": true,
4538 + "nulls": "last"
4539 + }
4540 + ],
4541 + "isUnique": false,
4542 + "concurrently": false,
4543 + "method": "btree",
4544 + "with": {}
4545 + },
4546 + "sales_price_idx": {
4547 + "name": "sales_price_idx",
4548 + "columns": [
4549 + {
4550 + "expression": "price_usd",
4551 + "isExpression": false,
4552 + "asc": true,
4553 + "nulls": "last"
4554 + }
4555 + ],
4556 + "isUnique": false,
4557 + "concurrently": false,
4558 + "method": "btree",
4559 + "with": {}
4560 + }
4561 + },
4562 + "foreignKeys": {},
4563 + "compositePrimaryKeys": {},
4564 + "uniqueConstraints": {},
4565 + "policies": {},
4566 + "checkConstraints": {},
4567 + "isRLSEnabled": false
4568 + },
4569 + "public.benchmarks": {
4570 + "name": "benchmarks",
4571 + "schema": "",
4572 + "columns": {
4573 + "ticker": {
4574 + "name": "ticker",
4575 + "type": "text",
4576 + "primaryKey": false,
4577 + "notNull": true
4578 + },
4579 + "date": {
4580 + "name": "date",
4581 + "type": "date",
4582 + "primaryKey": false,
4583 + "notNull": true
4584 + },
4585 + "value": {
4586 + "name": "value",
4587 + "type": "real",
4588 + "primaryKey": false,
4589 + "notNull": true
4590 + },
4591 + "source": {
4592 + "name": "source",
4593 + "type": "text",
4594 + "primaryKey": false,
4595 + "notNull": true
4596 + }
4597 + },
4598 + "indexes": {},
4599 + "foreignKeys": {},
4600 + "compositePrimaryKeys": {
4601 + "benchmarks_ticker_date_pk": {
4602 + "name": "benchmarks_ticker_date_pk",
4603 + "columns": [
4604 + "ticker",
4605 + "date"
4606 + ]
4607 + }
4608 + },
4609 + "uniqueConstraints": {},
4610 + "policies": {},
4611 + "checkConstraints": {},
4612 + "isRLSEnabled": false
4613 + },
4614 + "public.category_snapshots": {
4615 + "name": "category_snapshots",
4616 + "schema": "",
4617 + "columns": {
4618 + "category_slug": {
4619 + "name": "category_slug",
4620 + "type": "text",
4621 + "primaryKey": false,
4622 + "notNull": true
4623 + },
4624 + "date": {
4625 + "name": "date",
4626 + "type": "date",
4627 + "primaryKey": false,
4628 + "notNull": true
4629 + },
4630 + "index_value": {
4631 + "name": "index_value",
4632 + "type": "real",
4633 + "primaryKey": false,
4634 + "notNull": false
4635 + },
4636 + "tracked_assets": {
4637 + "name": "tracked_assets",
4638 + "type": "integer",
4639 + "primaryKey": false,
4640 + "notNull": true,
4641 + "default": 0
4642 + },
4643 + "assets_with_valuation": {
4644 + "name": "assets_with_valuation",
4645 + "type": "integer",
4646 + "primaryKey": false,
4647 + "notNull": true,
4648 + "default": 0
4649 + },
4650 + "sales": {
4651 + "name": "sales",
4652 + "type": "integer",
4653 + "primaryKey": false,
4654 + "notNull": true,
4655 + "default": 0
4656 + },
4657 + "volume_usd": {
4658 + "name": "volume_usd",
4659 + "type": "numeric(18, 4)",
4660 + "primaryKey": false,
4661 + "notNull": false
4662 + },
4663 + "median_sale_usd": {
4664 + "name": "median_sale_usd",
4665 + "type": "numeric(18, 4)",
4666 + "primaryKey": false,
4667 + "notNull": false
4668 + },
4669 + "active_listings": {
4670 + "name": "active_listings",
4671 + "type": "integer",
4672 + "primaryKey": false,
4673 + "notNull": true,
4674 + "default": 0
4675 + },
4676 + "market_cap_est_usd": {
4677 + "name": "market_cap_est_usd",
4678 + "type": "numeric(18, 4)",
4679 + "primaryKey": false,
4680 + "notNull": false
4681 + },
4682 + "liquidity_score": {
4683 + "name": "liquidity_score",
4684 + "type": "real",
4685 + "primaryKey": false,
4686 + "notNull": false
4687 + },
4688 + "change_1d": {
4689 + "name": "change_1d",
4690 + "type": "numeric(8, 6)",
4691 + "primaryKey": false,
4692 + "notNull": false
4693 + },
4694 + "change_7d": {
4695 + "name": "change_7d",
4696 + "type": "numeric(8, 6)",
4697 + "primaryKey": false,
4698 + "notNull": false
4699 + },
4700 + "change_30d": {
4701 + "name": "change_30d",
4702 + "type": "numeric(8, 6)",
4703 + "primaryKey": false,
4704 + "notNull": false
4705 + },
4706 + "change_1y": {
4707 + "name": "change_1y",
4708 + "type": "numeric(8, 6)",
4709 + "primaryKey": false,
4710 + "notNull": false
4711 + }
4712 + },
4713 + "indexes": {},
4714 + "foreignKeys": {},
4715 + "compositePrimaryKeys": {
4716 + "category_snapshots_category_slug_date_pk": {
4717 + "name": "category_snapshots_category_slug_date_pk",
4718 + "columns": [
4719 + "category_slug",
4720 + "date"
4721 + ]
4722 + }
4723 + },
4724 + "uniqueConstraints": {},
4725 + "policies": {},
4726 + "checkConstraints": {},
4727 + "isRLSEnabled": false
4728 + },
4729 + "public.correlations": {
4730 + "name": "correlations",
4731 + "schema": "",
4732 + "columns": {
4733 + "a": {
4734 + "name": "a",
4735 + "type": "text",
4736 + "primaryKey": false,
4737 + "notNull": true
4738 + },
4739 + "b": {
4740 + "name": "b",
4741 + "type": "text",
4742 + "primaryKey": false,
4743 + "notNull": true
4744 + },
4745 + "window_days": {
4746 + "name": "window_days",
4747 + "type": "integer",
4748 + "primaryKey": false,
4749 + "notNull": true
4750 + },
4751 + "coefficient": {
4752 + "name": "coefficient",
4753 + "type": "real",
4754 + "primaryKey": false,
4755 + "notNull": true
4756 + },
4757 + "observations": {
4758 + "name": "observations",
4759 + "type": "integer",
4760 + "primaryKey": false,
4761 + "notNull": true
4762 + },
4763 + "computed_at": {
4764 + "name": "computed_at",
4765 + "type": "timestamp with time zone",
4766 + "primaryKey": false,
4767 + "notNull": true
4768 + }
4769 + },
4770 + "indexes": {},
4771 + "foreignKeys": {},
4772 + "compositePrimaryKeys": {
4773 + "correlations_a_b_window_days_pk": {
4774 + "name": "correlations_a_b_window_days_pk",
4775 + "columns": [
4776 + "a",
4777 + "b",
4778 + "window_days"
4779 + ]
4780 + }
4781 + },
4782 + "uniqueConstraints": {},
4783 + "policies": {},
4784 + "checkConstraints": {},
4785 + "isRLSEnabled": false
4786 + },
4787 + "public.index_constituents": {
4788 + "name": "index_constituents",
4789 + "schema": "",
4790 + "columns": {
4791 + "index_id": {
4792 + "name": "index_id",
4793 + "type": "text",
4794 + "primaryKey": false,
4795 + "notNull": true
4796 + },
4797 + "asset_id": {
4798 + "name": "asset_id",
4799 + "type": "text",
4800 + "primaryKey": false,
4801 + "notNull": true
4802 + },
4803 + "variant_id": {
4804 + "name": "variant_id",
4805 + "type": "text",
4806 + "primaryKey": false,
4807 + "notNull": true,
4808 + "default": "''"
4809 + },
4810 + "weight": {
4811 + "name": "weight",
4812 + "type": "real",
4813 + "primaryKey": false,
4814 + "notNull": true,
4815 + "default": 1
4816 + },
4817 + "added_at": {
4818 + "name": "added_at",
4819 + "type": "date",
4820 + "primaryKey": false,
4821 + "notNull": true
4822 + },
4823 + "removed_at": {
4824 + "name": "removed_at",
4825 + "type": "date",
4826 + "primaryKey": false,
4827 + "notNull": false
4828 + },
4829 + "reason": {
4830 + "name": "reason",
4831 + "type": "text",
4832 + "primaryKey": false,
4833 + "notNull": false
4834 + }
4835 + },
4836 + "indexes": {
4837 + "index_constituents_asset_idx": {
4838 + "name": "index_constituents_asset_idx",
4839 + "columns": [
4840 + {
4841 + "expression": "asset_id",
4842 + "isExpression": false,
4843 + "asc": true,
4844 + "nulls": "last"
4845 + }
4846 + ],
4847 + "isUnique": false,
4848 + "concurrently": false,
4849 + "method": "btree",
4850 + "with": {}
4851 + }
4852 + },
4853 + "foreignKeys": {},
4854 + "compositePrimaryKeys": {
4855 + "index_constituents_index_id_asset_id_variant_id_added_at_pk": {
4856 + "name": "index_constituents_index_id_asset_id_variant_id_added_at_pk",
4857 + "columns": [
4858 + "index_id",
4859 + "asset_id",
4860 + "variant_id",
4861 + "added_at"
4862 + ]
4863 + }
4864 + },
4865 + "uniqueConstraints": {},
4866 + "policies": {},
4867 + "checkConstraints": {},
4868 + "isRLSEnabled": false
4869 + },
4870 + "public.index_values": {
4871 + "name": "index_values",
4872 + "schema": "",
4873 + "columns": {
4874 + "index_id": {
4875 + "name": "index_id",
4876 + "type": "text",
4877 + "primaryKey": false,
4878 + "notNull": true
4879 + },
4880 + "date": {
4881 + "name": "date",
4882 + "type": "date",
4883 + "primaryKey": false,
4884 + "notNull": true
4885 + },
4886 + "value": {
4887 + "name": "value",
4888 + "type": "real",
4889 + "primaryKey": false,
4890 + "notNull": true
4891 + },
4892 + "constituents_count": {
4893 + "name": "constituents_count",
4894 + "type": "integer",
4895 + "primaryKey": false,
4896 + "notNull": true,
4897 + "default": 0
4898 + },
4899 + "transactions": {
4900 + "name": "transactions",
4901 + "type": "integer",
4902 + "primaryKey": false,
4903 + "notNull": true,
4904 + "default": 0
4905 + },
4906 + "volume_usd": {
4907 + "name": "volume_usd",
4908 + "type": "numeric(18, 4)",
4909 + "primaryKey": false,
4910 + "notNull": false
4911 + },
4912 + "median_sale_usd": {
4913 + "name": "median_sale_usd",
4914 + "type": "numeric(18, 4)",
4915 + "primaryKey": false,
4916 + "notNull": false
4917 + },
4918 + "avg_sale_usd": {
4919 + "name": "avg_sale_usd",
4920 + "type": "numeric(18, 4)",
4921 + "primaryKey": false,
4922 + "notNull": false
4923 + },
4924 + "market_cap_est_usd": {
4925 + "name": "market_cap_est_usd",
4926 + "type": "numeric(18, 4)",
4927 + "primaryKey": false,
4928 + "notNull": false
4929 + },
4930 + "market_cap_confidence": {
4931 + "name": "market_cap_confidence",
4932 + "type": "text",
4933 + "primaryKey": false,
4934 + "notNull": false
4935 + },
4936 + "liquidity_score": {
4937 + "name": "liquidity_score",
4938 + "type": "real",
4939 + "primaryKey": false,
4940 + "notNull": false
4941 + },
4942 + "momentum": {
4943 + "name": "momentum",
4944 + "type": "real",
4945 + "primaryKey": false,
4946 + "notNull": false
4947 + },
4948 + "breadth": {
4949 + "name": "breadth",
4950 + "type": "integer",
4951 + "primaryKey": false,
4952 + "notNull": false
4953 + },
4954 + "tracked_assets": {
4955 + "name": "tracked_assets",
4956 + "type": "integer",
4957 + "primaryKey": false,
4958 + "notNull": false
4959 + },
4960 + "coverage": {
4961 + "name": "coverage",
4962 + "type": "real",
4963 + "primaryKey": false,
4964 + "notNull": false
4965 + }
4966 + },
4967 + "indexes": {},
4968 + "foreignKeys": {},
4969 + "compositePrimaryKeys": {
4970 + "index_values_index_id_date_pk": {
4971 + "name": "index_values_index_id_date_pk",
4972 + "columns": [
4973 + "index_id",
4974 + "date"
4975 + ]
4976 + }
4977 + },
4978 + "uniqueConstraints": {},
4979 + "policies": {},
4980 + "checkConstraints": {},
4981 + "isRLSEnabled": false
4982 + },
4983 + "public.indices": {
4984 + "name": "indices",
4985 + "schema": "",
4986 + "columns": {
4987 + "id": {
4988 + "name": "id",
4989 + "type": "text",
4990 + "primaryKey": true,
4991 + "notNull": true
4992 + },
4993 + "ticker": {
4994 + "name": "ticker",
4995 + "type": "text",
4996 + "primaryKey": false,
4997 + "notNull": true
4998 + },
4999 + "name": {
5000 + "name": "name",
5001 + "type": "text",
5002 + "primaryKey": false,
5003 + "notNull": true
5004 + },
5005 + "description": {
5006 + "name": "description",
5007 + "type": "text",
5008 + "primaryKey": false,
5009 + "notNull": false
5010 + },
5011 + "category_slugs": {
5012 + "name": "category_slugs",
5013 + "type": "text[]",
5014 + "primaryKey": false,
5015 + "notNull": true,
5016 + "default": "'{}'::text[]"
5017 + },
5018 + "family_slugs": {
5019 + "name": "family_slugs",
5020 + "type": "text[]",
5021 + "primaryKey": false,
5022 + "notNull": true,
5023 + "default": "'{}'::text[]"
5024 + },
5025 + "parent_ticker": {
5026 + "name": "parent_ticker",
5027 + "type": "text",
5028 + "primaryKey": false,
5029 + "notNull": false
5030 + },
5031 + "methodology": {
5032 + "name": "methodology",
5033 + "type": "text",
5034 + "primaryKey": false,
5035 + "notNull": true,
5036 + "default": "'chain_linked_equal_weight_v1'"
5037 + },
5038 + "weighting": {
5039 + "name": "weighting",
5040 + "type": "text",
5041 + "primaryKey": false,
5042 + "notNull": true,
5043 + "default": "'equal'"
5044 + },
5045 + "base_date": {
5046 + "name": "base_date",
5047 + "type": "date",
5048 + "primaryKey": false,
5049 + "notNull": true
5050 + },
5051 + "base_value": {
5052 + "name": "base_value",
5053 + "type": "real",
5054 + "primaryKey": false,
5055 + "notNull": true,
5056 + "default": 1000
5057 + },
5058 + "min_constituents": {
5059 + "name": "min_constituents",
5060 + "type": "integer",
5061 + "primaryKey": false,
5062 + "notNull": true,
5063 + "default": 10
5064 + },
5065 + "active": {
5066 + "name": "active",
5067 + "type": "boolean",
5068 + "primaryKey": false,
5069 + "notNull": true,
5070 + "default": true
5071 + },
5072 + "is_flagship": {
5073 + "name": "is_flagship",
5074 + "type": "boolean",
5075 + "primaryKey": false,
5076 + "notNull": true,
5077 + "default": false
5078 + },
5079 + "color": {
5080 + "name": "color",
5081 + "type": "text",
5082 + "primaryKey": false,
5083 + "notNull": false
5084 + },
5085 + "created_at": {
5086 + "name": "created_at",
5087 + "type": "timestamp with time zone",
5088 + "primaryKey": false,
5089 + "notNull": true,
5090 + "default": "now()"
5091 + }
5092 + },
5093 + "indexes": {},
5094 + "foreignKeys": {},
5095 + "compositePrimaryKeys": {},
5096 + "uniqueConstraints": {
5097 + "indices_ticker_unique": {
5098 + "name": "indices_ticker_unique",
5099 + "nullsNotDistinct": false,
5100 + "columns": [
5101 + "ticker"
5102 + ]
5103 + }
5104 + },
5105 + "policies": {},
5106 + "checkConstraints": {},
5107 + "isRLSEnabled": false
5108 + },
5109 + "public.price_snapshots": {
5110 + "name": "price_snapshots",
5111 + "schema": "",
5112 + "columns": {
5113 + "asset_id": {
5114 + "name": "asset_id",
5115 + "type": "text",
5116 + "primaryKey": false,
5117 + "notNull": true
5118 + },
5119 + "variant_id": {
5120 + "name": "variant_id",
5121 + "type": "text",
5122 + "primaryKey": false,
5123 + "notNull": true,
5124 + "default": "''"
5125 + },
5126 + "date": {
5127 + "name": "date",
5128 + "type": "date",
5129 + "primaryKey": false,
5130 + "notNull": true
5131 + },
5132 + "riv_usd": {
5133 + "name": "riv_usd",
5134 + "type": "numeric(18, 4)",
5135 + "primaryKey": false,
5136 + "notNull": false
5137 + },
5138 + "latest_sale_usd": {
5139 + "name": "latest_sale_usd",
5140 + "type": "numeric(18, 4)",
5141 + "primaryKey": false,
5142 + "notNull": false
5143 + },
5144 + "median_usd": {
5145 + "name": "median_usd",
5146 + "type": "numeric(18, 4)",
5147 + "primaryKey": false,
5148 + "notNull": false
5149 + },
5150 + "sales_count": {
5151 + "name": "sales_count",
5152 + "type": "integer",
5153 + "primaryKey": false,
5154 + "notNull": true,
5155 + "default": 0
5156 + },
5157 + "volume_usd": {
5158 + "name": "volume_usd",
5159 + "type": "numeric(18, 4)",
5160 + "primaryKey": false,
5161 + "notNull": false
5162 + },
5163 + "listings_count": {
5164 + "name": "listings_count",
5165 + "type": "integer",
5166 + "primaryKey": false,
5167 + "notNull": true,
5168 + "default": 0
5169 + },
5170 + "min_ask_usd": {
5171 + "name": "min_ask_usd",
5172 + "type": "numeric(18, 4)",
5173 + "primaryKey": false,
5174 + "notNull": false
5175 + },
5176 + "observation_usd": {
5177 + "name": "observation_usd",
5178 + "type": "numeric(18, 4)",
5179 + "primaryKey": false,
5180 + "notNull": false
5181 + }
5182 + },
5183 + "indexes": {
5184 + "price_snapshots_date_idx": {
5185 + "name": "price_snapshots_date_idx",
5186 + "columns": [
5187 + {
5188 + "expression": "date",
5189 + "isExpression": false,
5190 + "asc": true,
5191 + "nulls": "last"
5192 + }
5193 + ],
5194 + "isUnique": false,
5195 + "concurrently": false,
5196 + "method": "btree",
5197 + "with": {}
5198 + }
5199 + },
5200 + "foreignKeys": {},
5201 + "compositePrimaryKeys": {
5202 + "price_snapshots_asset_id_variant_id_date_pk": {
5203 + "name": "price_snapshots_asset_id_variant_id_date_pk",
5204 + "columns": [
5205 + "asset_id",
5206 + "variant_id",
5207 + "date"
5208 + ]
5209 + }
5210 + },
5211 + "uniqueConstraints": {},
5212 + "policies": {},
5213 + "checkConstraints": {},
5214 + "isRLSEnabled": false
5215 + },
5216 + "public.radar_findings": {
5217 + "name": "radar_findings",
5218 + "schema": "",
5219 + "columns": {
5220 + "id": {
5221 + "name": "id",
5222 + "type": "text",
5223 + "primaryKey": true,
5224 + "notNull": true
5225 + },
5226 + "asset_id": {
5227 + "name": "asset_id",
5228 + "type": "text",
5229 + "primaryKey": false,
5230 + "notNull": true
5231 + },
5232 + "kind": {
5233 + "name": "kind",
5234 + "type": "text",
5235 + "primaryKey": false,
5236 + "notNull": true
5237 + },
5238 + "score": {
5239 + "name": "score",
5240 + "type": "real",
5241 + "primaryKey": false,
5242 + "notNull": true
5243 + },
5244 + "evidence": {
5245 + "name": "evidence",
5246 + "type": "jsonb",
5247 + "primaryKey": false,
5248 + "notNull": true,
5249 + "default": "'{}'::jsonb"
5250 + },
5251 + "entity_type": {
5252 + "name": "entity_type",
5253 + "type": "text",
5254 + "primaryKey": false,
5255 + "notNull": false
5256 + },
5257 + "entity_id": {
5258 + "name": "entity_id",
5259 + "type": "text",
5260 + "primaryKey": false,
5261 + "notNull": false
5262 + },
5263 + "detected_at": {
5264 + "name": "detected_at",
5265 + "type": "timestamp with time zone",
5266 + "primaryKey": false,
5267 + "notNull": true
5268 + },
5269 + "expires_at": {
5270 + "name": "expires_at",
5271 + "type": "timestamp with time zone",
5272 + "primaryKey": false,
5273 + "notNull": false
5274 + }
5275 + },
5276 + "indexes": {
5277 + "radar_kind_idx": {
5278 + "name": "radar_kind_idx",
5279 + "columns": [
5280 + {
5281 + "expression": "kind",
5282 + "isExpression": false,
5283 + "asc": true,
5284 + "nulls": "last"
5285 + },
5286 + {
5287 + "expression": "detected_at",
5288 + "isExpression": false,
5289 + "asc": true,
5290 + "nulls": "last"
5291 + }
5292 + ],
5293 + "isUnique": false,
5294 + "concurrently": false,
5295 + "method": "btree",
5296 + "with": {}
5297 + },
5298 + "radar_entity_uq": {
5299 + "name": "radar_entity_uq",
5300 + "columns": [
5301 + {
5302 + "expression": "kind",
5303 + "isExpression": false,
5304 + "asc": true,
5305 + "nulls": "last"
5306 + },
5307 + {
5308 + "expression": "entity_type",
5309 + "isExpression": false,
5310 + "asc": true,
5311 + "nulls": "last"
5312 + },
5313 + {
5314 + "expression": "entity_id",
5315 + "isExpression": false,
5316 + "asc": true,
5317 + "nulls": "last"
5318 + }
5319 + ],
5320 + "isUnique": true,
5321 + "concurrently": false,
5322 + "method": "btree",
5323 + "with": {}
5324 + }
5325 + },
5326 + "foreignKeys": {},
5327 + "compositePrimaryKeys": {},
5328 + "uniqueConstraints": {},
5329 + "policies": {},
5330 + "checkConstraints": {},
5331 + "isRLSEnabled": false
5332 + },
5333 + "public.valuations": {
5334 + "name": "valuations",
5335 + "schema": "",
5336 + "columns": {
5337 + "id": {
5338 + "name": "id",
5339 + "type": "text",
5340 + "primaryKey": true,
5341 + "notNull": true
5342 + },
5343 + "asset_id": {
5344 + "name": "asset_id",
5345 + "type": "text",
5346 + "primaryKey": false,
5347 + "notNull": true
5348 + },
5349 + "variant_id": {
5350 + "name": "variant_id",
5351 + "type": "text",
5352 + "primaryKey": false,
5353 + "notNull": false
5354 + },
5355 + "computed_at": {
5356 + "name": "computed_at",
5357 + "type": "timestamp with time zone",
5358 + "primaryKey": false,
5359 + "notNull": true
5360 + },
5361 + "riv_usd": {
5362 + "name": "riv_usd",
5363 + "type": "numeric(18, 4)",
5364 + "primaryKey": false,
5365 + "notNull": false
5366 + },
5367 + "low_usd": {
5368 + "name": "low_usd",
5369 + "type": "numeric(18, 4)",
5370 + "primaryKey": false,
5371 + "notNull": false
5372 + },
5373 + "high_usd": {
5374 + "name": "high_usd",
5375 + "type": "numeric(18, 4)",
5376 + "primaryKey": false,
5377 + "notNull": false
5378 + },
5379 + "confidence": {
5380 + "name": "confidence",
5381 + "type": "numeric(8, 6)",
5382 + "primaryKey": false,
5383 + "notNull": true,
5384 + "default": 0
5385 + },
5386 + "confidence_label": {
5387 + "name": "confidence_label",
5388 + "type": "text",
5389 + "primaryKey": false,
5390 + "notNull": true,
5391 + "default": "'insufficient'"
5392 + },
5393 + "sample_size": {
5394 + "name": "sample_size",
5395 + "type": "integer",
5396 + "primaryKey": false,
5397 + "notNull": true,
5398 + "default": 0
5399 + },
5400 + "window_days": {
5401 + "name": "window_days",
5402 + "type": "integer",
5403 + "primaryKey": false,
5404 + "notNull": true,
5405 + "default": 365
5406 + },
5407 + "methods": {
5408 + "name": "methods",
5409 + "type": "jsonb",
5410 + "primaryKey": false,
5411 + "notNull": true,
5412 + "default": "'{}'::jsonb"
5413 + },
5414 + "sales_used": {
5415 + "name": "sales_used",
5416 + "type": "text[]",
5417 + "primaryKey": false,
5418 + "notNull": true,
5419 + "default": "'{}'::text[]"
5420 + },
5421 + "observations_used": {
5422 + "name": "observations_used",
5423 + "type": "integer",
5424 + "primaryKey": false,
5425 + "notNull": true,
5426 + "default": 0
5427 + },
5428 + "method": {
5429 + "name": "method",
5430 + "type": "text",
5431 + "primaryKey": false,
5432 + "notNull": true,
5433 + "default": "'ensemble_v1'"
5434 + },
5435 + "notes": {
5436 + "name": "notes",
5437 + "type": "text[]",
5438 + "primaryKey": false,
5439 + "notNull": true,
5440 + "default": "'{}'::text[]"
5441 + }
5442 + },
5443 + "indexes": {
5444 + "valuations_asset_idx": {
5445 + "name": "valuations_asset_idx",
5446 + "columns": [
5447 + {
5448 + "expression": "asset_id",
5449 + "isExpression": false,
5450 + "asc": true,
5451 + "nulls": "last"
5452 + },
5453 + {
5454 + "expression": "computed_at",
5455 + "isExpression": false,
5456 + "asc": true,
5457 + "nulls": "last"
5458 + }
5459 + ],
5460 + "isUnique": false,
5461 + "concurrently": false,
5462 + "method": "btree",
5463 + "with": {}
5464 + },
5465 + "valuations_variant_idx": {
5466 + "name": "valuations_variant_idx",
5467 + "columns": [
5468 + {
5469 + "expression": "variant_id",
5470 + "isExpression": false,
5471 + "asc": true,
5472 + "nulls": "last"
5473 + },
5474 + {
5475 + "expression": "computed_at",
5476 + "isExpression": false,
5477 + "asc": true,
5478 + "nulls": "last"
5479 + }
5480 + ],
5481 + "isUnique": false,
5482 + "concurrently": false,
5483 + "method": "btree",
5484 + "with": {}
5485 + }
5486 + },
5487 + "foreignKeys": {},
5488 + "compositePrimaryKeys": {},
5489 + "uniqueConstraints": {},
5490 + "policies": {},
5491 + "checkConstraints": {},
5492 + "isRLSEnabled": false
5493 + },
5494 + "public.alert_events": {
5495 + "name": "alert_events",
5496 + "schema": "",
5497 + "columns": {
5498 + "id": {
5499 + "name": "id",
5500 + "type": "text",
5501 + "primaryKey": true,
5502 + "notNull": true
5503 + },
5504 + "alert_id": {
5505 + "name": "alert_id",
5506 + "type": "text",
5507 + "primaryKey": false,
5508 + "notNull": true
5509 + },
5510 + "user_id": {
5511 + "name": "user_id",
5512 + "type": "text",
5513 + "primaryKey": false,
5514 + "notNull": true
5515 + },
5516 + "message": {
5517 + "name": "message",
5518 + "type": "text",
5519 + "primaryKey": false,
5520 + "notNull": true
5521 + },
5522 + "payload": {
5523 + "name": "payload",
5524 + "type": "jsonb",
5525 + "primaryKey": false,
5526 + "notNull": true,
5527 + "default": "'{}'::jsonb"
5528 + },
5529 + "read_at": {
5530 + "name": "read_at",
5531 + "type": "timestamp with time zone",
5532 + "primaryKey": false,
5533 + "notNull": false
5534 + },
5535 + "created_at": {
5536 + "name": "created_at",
5537 + "type": "timestamp with time zone",
5538 + "primaryKey": false,
5539 + "notNull": true,
5540 + "default": "now()"
5541 + }
5542 + },
5543 + "indexes": {
5544 + "alert_events_user_idx": {
5545 + "name": "alert_events_user_idx",
5546 + "columns": [
5547 + {
5548 + "expression": "user_id",
5549 + "isExpression": false,
5550 + "asc": true,
5551 + "nulls": "last"
5552 + },
5553 + {
5554 + "expression": "created_at",
5555 + "isExpression": false,
5556 + "asc": true,
5557 + "nulls": "last"
5558 + }
5559 + ],
5560 + "isUnique": false,
5561 + "concurrently": false,
5562 + "method": "btree",
5563 + "with": {}
5564 + }
5565 + },
5566 + "foreignKeys": {},
5567 + "compositePrimaryKeys": {},
5568 + "uniqueConstraints": {},
5569 + "policies": {},
5570 + "checkConstraints": {},
5571 + "isRLSEnabled": false
5572 + },
5573 + "public.alerts": {
5574 + "name": "alerts",
5575 + "schema": "",
5576 + "columns": {
5577 + "id": {
5578 + "name": "id",
5579 + "type": "text",
5580 + "primaryKey": true,
5581 + "notNull": true
5582 + },
5583 + "user_id": {
5584 + "name": "user_id",
5585 + "type": "text",
5586 + "primaryKey": false,
5587 + "notNull": true
5588 + },
5589 + "alert_type": {
5590 + "name": "alert_type",
5591 + "type": "text",
5592 + "primaryKey": false,
5593 + "notNull": true
5594 + },
5595 + "target_type": {
5596 + "name": "target_type",
5597 + "type": "text",
5598 + "primaryKey": false,
5599 + "notNull": true
5600 + },
5601 + "target_id": {
5602 + "name": "target_id",
5603 + "type": "text",
5604 + "primaryKey": false,
5605 + "notNull": true
5606 + },
5607 + "threshold": {
5608 + "name": "threshold",
5609 + "type": "numeric(18, 4)",
5610 + "primaryKey": false,
5611 + "notNull": false
5612 + },
5613 + "currency": {
5614 + "name": "currency",
5615 + "type": "text",
5616 + "primaryKey": false,
5617 + "notNull": false
5618 + },
5619 + "params": {
5620 + "name": "params",
5621 + "type": "jsonb",
5622 + "primaryKey": false,
5623 + "notNull": true,
5624 + "default": "'{}'::jsonb"
5625 + },
5626 + "channel": {
5627 + "name": "channel",
5628 + "type": "text",
5629 + "primaryKey": false,
5630 + "notNull": true,
5631 + "default": "'inapp'"
5632 + },
5633 + "active": {
5634 + "name": "active",
5635 + "type": "boolean",
5636 + "primaryKey": false,
5637 + "notNull": true,
5638 + "default": true
5639 + },
5640 + "last_triggered_at": {
5641 + "name": "last_triggered_at",
5642 + "type": "timestamp with time zone",
5643 + "primaryKey": false,
5644 + "notNull": false
5645 + },
5646 + "created_at": {
5647 + "name": "created_at",
5648 + "type": "timestamp with time zone",
5649 + "primaryKey": false,
5650 + "notNull": true,
5651 + "default": "now()"
5652 + }
5653 + },
5654 + "indexes": {
5655 + "alerts_user_idx": {
5656 + "name": "alerts_user_idx",
5657 + "columns": [
5658 + {
5659 + "expression": "user_id",
5660 + "isExpression": false,
5661 + "asc": true,
5662 + "nulls": "last"
5663 + }
5664 + ],
5665 + "isUnique": false,
5666 + "concurrently": false,
5667 + "method": "btree",
5668 + "with": {}
5669 + },
5670 + "alerts_target_idx": {
5671 + "name": "alerts_target_idx",
5672 + "columns": [
5673 + {
5674 + "expression": "target_type",
5675 + "isExpression": false,
5676 + "asc": true,
5677 + "nulls": "last"
5678 + },
5679 + {
5680 + "expression": "target_id",
5681 + "isExpression": false,
5682 + "asc": true,
5683 + "nulls": "last"
5684 + },
5685 + {
5686 + "expression": "active",
5687 + "isExpression": false,
5688 + "asc": true,
5689 + "nulls": "last"
5690 + }
5691 + ],
5692 + "isUnique": false,
5693 + "concurrently": false,
5694 + "method": "btree",
5695 + "with": {}
5696 + }
5697 + },
5698 + "foreignKeys": {},
5699 + "compositePrimaryKeys": {},
5700 + "uniqueConstraints": {},
5701 + "policies": {},
5702 + "checkConstraints": {},
5703 + "isRLSEnabled": false
5704 + },
5705 + "public.api_keys": {
5706 + "name": "api_keys",
5707 + "schema": "",
5708 + "columns": {
5709 + "id": {
5710 + "name": "id",
5711 + "type": "text",
5712 + "primaryKey": true,
5713 + "notNull": true
5714 + },
5715 + "user_id": {
5716 + "name": "user_id",
5717 + "type": "text",
5718 + "primaryKey": false,
5719 + "notNull": false
5720 + },
5721 + "name": {
5722 + "name": "name",
5723 + "type": "text",
5724 + "primaryKey": false,
5725 + "notNull": true
5726 + },
5727 + "prefix": {
5728 + "name": "prefix",
5729 + "type": "text",
5730 + "primaryKey": false,
5731 + "notNull": true
5732 + },
5733 + "key_hash": {
5734 + "name": "key_hash",
5735 + "type": "text",
5736 + "primaryKey": false,
5737 + "notNull": true
5738 + },
5739 + "tier": {
5740 + "name": "tier",
5741 + "type": "text",
5742 + "primaryKey": false,
5743 + "notNull": true,
5744 + "default": "'free'"
5745 + },
5746 + "rate_limit_per_minute": {
5747 + "name": "rate_limit_per_minute",
5748 + "type": "integer",
5749 + "primaryKey": false,
5750 + "notNull": true,
5751 + "default": 60
5752 + },
5753 + "daily_quota": {
5754 + "name": "daily_quota",
5755 + "type": "integer",
5756 + "primaryKey": false,
5757 + "notNull": true,
5758 + "default": 1000
5759 + },
5760 + "created_at": {
5761 + "name": "created_at",
5762 + "type": "timestamp with time zone",
5763 + "primaryKey": false,
5764 + "notNull": true,
5765 + "default": "now()"
5766 + },
5767 + "last_used_at": {
5768 + "name": "last_used_at",
5769 + "type": "timestamp with time zone",
5770 + "primaryKey": false,
5771 + "notNull": false
5772 + },
5773 + "revoked_at": {
5774 + "name": "revoked_at",
5775 + "type": "timestamp with time zone",
5776 + "primaryKey": false,
5777 + "notNull": false
5778 + }
5779 + },
5780 + "indexes": {
5781 + "api_keys_user_idx": {
5782 + "name": "api_keys_user_idx",
5783 + "columns": [
5784 + {
5785 + "expression": "user_id",
5786 + "isExpression": false,
5787 + "asc": true,
5788 + "nulls": "last"
5789 + }
5790 + ],
5791 + "isUnique": false,
5792 + "concurrently": false,
5793 + "method": "btree",
5794 + "with": {}
5795 + }
5796 + },
5797 + "foreignKeys": {},
5798 + "compositePrimaryKeys": {},
5799 + "uniqueConstraints": {
5800 + "api_keys_key_hash_unique": {
5801 + "name": "api_keys_key_hash_unique",
5802 + "nullsNotDistinct": false,
5803 + "columns": [
5804 + "key_hash"
5805 + ]
5806 + }
5807 + },
5808 + "policies": {},
5809 + "checkConstraints": {},
5810 + "isRLSEnabled": false
5811 + },
5812 + "public.api_usage": {
5813 + "name": "api_usage",
5814 + "schema": "",
5815 + "columns": {
5816 + "key_id": {
5817 + "name": "key_id",
5818 + "type": "text",
5819 + "primaryKey": false,
5820 + "notNull": true
5821 + },
5822 + "date": {
5823 + "name": "date",
5824 + "type": "date",
5825 + "primaryKey": false,
5826 + "notNull": true
5827 + },
5828 + "endpoint": {
5829 + "name": "endpoint",
5830 + "type": "text",
5831 + "primaryKey": false,
5832 + "notNull": true
5833 + },
5834 + "count": {
5835 + "name": "count",
5836 + "type": "integer",
5837 + "primaryKey": false,
5838 + "notNull": true,
5839 + "default": 0
5840 + },
5841 + "latency_ms_avg": {
5842 + "name": "latency_ms_avg",
5843 + "type": "real",
5844 + "primaryKey": false,
5845 + "notNull": false
5846 + }
5847 + },
5848 + "indexes": {},
5849 + "foreignKeys": {},
5850 + "compositePrimaryKeys": {
5851 + "api_usage_key_id_date_endpoint_pk": {
5852 + "name": "api_usage_key_id_date_endpoint_pk",
5853 + "columns": [
5854 + "key_id",
5855 + "date",
5856 + "endpoint"
5857 + ]
5858 + }
5859 + },
5860 + "uniqueConstraints": {},
5861 + "policies": {},
5862 + "checkConstraints": {},
5863 + "isRLSEnabled": false
5864 + },
5865 + "public.asset_views": {
5866 + "name": "asset_views",
5867 + "schema": "",
5868 + "columns": {
5869 + "asset_id": {
5870 + "name": "asset_id",
5871 + "type": "text",
5872 + "primaryKey": false,
5873 + "notNull": true
5874 + },
5875 + "date": {
5876 + "name": "date",
5877 + "type": "date",
5878 + "primaryKey": false,
5879 + "notNull": true
5880 + },
5881 + "views": {
5882 + "name": "views",
5883 + "type": "integer",
5884 + "primaryKey": false,
5885 + "notNull": true,
5886 + "default": 0
5887 + }
5888 + },
5889 + "indexes": {},
5890 + "foreignKeys": {},
5891 + "compositePrimaryKeys": {
5892 + "asset_views_asset_id_date_pk": {
5893 + "name": "asset_views_asset_id_date_pk",
5894 + "columns": [
5895 + "asset_id",
5896 + "date"
5897 + ]
5898 + }
5899 + },
5900 + "uniqueConstraints": {},
5901 + "policies": {},
5902 + "checkConstraints": {},
5903 + "isRLSEnabled": false
5904 + },
5905 + "public.collection_items": {
5906 + "name": "collection_items",
5907 + "schema": "",
5908 + "columns": {
5909 + "id": {
5910 + "name": "id",
5911 + "type": "text",
5912 + "primaryKey": true,
5913 + "notNull": true
5914 + },
5915 + "collection_id": {
5916 + "name": "collection_id",
5917 + "type": "text",
5918 + "primaryKey": false,
5919 + "notNull": true
5920 + },
5921 + "asset_id": {
5922 + "name": "asset_id",
5923 + "type": "text",
5924 + "primaryKey": false,
5925 + "notNull": true
5926 + },
5927 + "variant_id": {
5928 + "name": "variant_id",
5929 + "type": "text",
5930 + "primaryKey": false,
5931 + "notNull": false
5932 + },
5933 + "quantity": {
5934 + "name": "quantity",
5935 + "type": "integer",
5936 + "primaryKey": false,
5937 + "notNull": true,
5938 + "default": 1
5939 + },
5940 + "acquired_at": {
5941 + "name": "acquired_at",
5942 + "type": "date",
5943 + "primaryKey": false,
5944 + "notNull": false
5945 + },
5946 + "purchase_price": {
5947 + "name": "purchase_price",
5948 + "type": "numeric(18, 4)",
5949 + "primaryKey": false,
5950 + "notNull": false
5951 + },
5952 + "purchase_currency": {
5953 + "name": "purchase_currency",
5954 + "type": "text",
5955 + "primaryKey": false,
5956 + "notNull": false
5957 + },
5958 + "purchase_price_usd": {
5959 + "name": "purchase_price_usd",
5960 + "type": "numeric(18, 4)",
5961 + "primaryKey": false,
5962 + "notNull": false
5963 + },
5964 + "source": {
5965 + "name": "source",
5966 + "type": "text",
5967 + "primaryKey": false,
5968 + "notNull": false
5969 + },
5970 + "grader": {
5971 + "name": "grader",
5972 + "type": "text",
5973 + "primaryKey": false,
5974 + "notNull": false
5975 + },
5976 + "grade": {
5977 + "name": "grade",
5978 + "type": "text",
5979 + "primaryKey": false,
5980 + "notNull": false
5981 + },
5982 + "certification_number": {
5983 + "name": "certification_number",
5984 + "type": "text",
5985 + "primaryKey": false,
5986 + "notNull": false
5987 + },
5988 + "serial": {
5989 + "name": "serial",
5990 + "type": "text",
5991 + "primaryKey": false,
5992 + "notNull": false
5993 + },
5994 + "photos": {
5995 + "name": "photos",
5996 + "type": "jsonb",
5997 + "primaryKey": false,
5998 + "notNull": true,
5999 + "default": "'[]'::jsonb"
6000 + },
6001 + "notes": {
6002 + "name": "notes",
6003 + "type": "text",
6004 + "primaryKey": false,
6005 + "notNull": false
6006 + },
6007 + "created_at": {
6008 + "name": "created_at",
6009 + "type": "timestamp with time zone",
6010 + "primaryKey": false,
6011 + "notNull": true,
6012 + "default": "now()"
6013 + },
6014 + "updated_at": {
6015 + "name": "updated_at",
6016 + "type": "timestamp with time zone",
6017 + "primaryKey": false,
6018 + "notNull": true,
6019 + "default": "now()"
6020 + }
6021 + },
6022 + "indexes": {
6023 + "collection_items_collection_idx": {
6024 + "name": "collection_items_collection_idx",
6025 + "columns": [
6026 + {
6027 + "expression": "collection_id",
6028 + "isExpression": false,
6029 + "asc": true,
6030 + "nulls": "last"
6031 + }
6032 + ],
6033 + "isUnique": false,
6034 + "concurrently": false,
6035 + "method": "btree",
6036 + "with": {}
6037 + },
6038 + "collection_items_asset_idx": {
6039 + "name": "collection_items_asset_idx",
6040 + "columns": [
6041 + {
6042 + "expression": "asset_id",
6043 + "isExpression": false,
6044 + "asc": true,
6045 + "nulls": "last"
6046 + }
6047 + ],
6048 + "isUnique": false,
6049 + "concurrently": false,
6050 + "method": "btree",
6051 + "with": {}
6052 + }
6053 + },
6054 + "foreignKeys": {},
6055 + "compositePrimaryKeys": {},
6056 + "uniqueConstraints": {},
6057 + "policies": {},
6058 + "checkConstraints": {},
6059 + "isRLSEnabled": false
6060 + },
6061 + "public.collection_snapshots": {
6062 + "name": "collection_snapshots",
6063 + "schema": "",
6064 + "columns": {
6065 + "collection_id": {
6066 + "name": "collection_id",
6067 + "type": "text",
6068 + "primaryKey": false,
6069 + "notNull": true
6070 + },
6071 + "date": {
6072 + "name": "date",
6073 + "type": "date",
6074 + "primaryKey": false,
6075 + "notNull": true
6076 + },
6077 + "value_usd": {
6078 + "name": "value_usd",
6079 + "type": "numeric(18, 4)",
6080 + "primaryKey": false,
6081 + "notNull": true
6082 + },
6083 + "cost_basis_usd": {
6084 + "name": "cost_basis_usd",
6085 + "type": "numeric(18, 4)",
6086 + "primaryKey": false,
6087 + "notNull": true
6088 + },
6089 + "items": {
6090 + "name": "items",
6091 + "type": "integer",
6092 + "primaryKey": false,
6093 + "notNull": true
6094 + }
6095 + },
6096 + "indexes": {},
6097 + "foreignKeys": {},
6098 + "compositePrimaryKeys": {
6099 + "collection_snapshots_collection_id_date_pk": {
6100 + "name": "collection_snapshots_collection_id_date_pk",
6101 + "columns": [
6102 + "collection_id",
6103 + "date"
6104 + ]
6105 + }
6106 + },
6107 + "uniqueConstraints": {},
6108 + "policies": {},
6109 + "checkConstraints": {},
6110 + "isRLSEnabled": false
6111 + },
6112 + "public.collections": {
6113 + "name": "collections",
6114 + "schema": "",
6115 + "columns": {
6116 + "id": {
6117 + "name": "id",
6118 + "type": "text",
6119 + "primaryKey": true,
6120 + "notNull": true
6121 + },
6122 + "user_id": {
6123 + "name": "user_id",
6124 + "type": "text",
6125 + "primaryKey": false,
6126 + "notNull": true
6127 + },
6128 + "name": {
6129 + "name": "name",
6130 + "type": "text",
6131 + "primaryKey": false,
6132 + "notNull": true
6133 + },
6134 + "description": {
6135 + "name": "description",
6136 + "type": "text",
6137 + "primaryKey": false,
6138 + "notNull": false
6139 + },
6140 + "is_public": {
6141 + "name": "is_public",
6142 + "type": "boolean",
6143 + "primaryKey": false,
6144 + "notNull": true,
6145 + "default": false
6146 + },
6147 + "public_slug": {
6148 + "name": "public_slug",
6149 + "type": "text",
6150 + "primaryKey": false,
6151 + "notNull": false
6152 + },
6153 + "created_at": {
6154 + "name": "created_at",
6155 + "type": "timestamp with time zone",
6156 + "primaryKey": false,
6157 + "notNull": true,
6158 + "default": "now()"
6159 + },
6160 + "updated_at": {
6161 + "name": "updated_at",
6162 + "type": "timestamp with time zone",
6163 + "primaryKey": false,
6164 + "notNull": true,
6165 + "default": "now()"
6166 + }
6167 + },
6168 + "indexes": {
6169 + "collections_user_idx": {
6170 + "name": "collections_user_idx",
6171 + "columns": [
6172 + {
6173 + "expression": "user_id",
6174 + "isExpression": false,
6175 + "asc": true,
6176 + "nulls": "last"
6177 + }
6178 + ],
6179 + "isUnique": false,
6180 + "concurrently": false,
6181 + "method": "btree",
6182 + "with": {}
6183 + },
6184 + "collections_public_slug_uq": {
6185 + "name": "collections_public_slug_uq",
6186 + "columns": [
6187 + {
6188 + "expression": "public_slug",
6189 + "isExpression": false,
6190 + "asc": true,
6191 + "nulls": "last"
6192 + }
6193 + ],
6194 + "isUnique": true,
6195 + "concurrently": false,
6196 + "method": "btree",
6197 + "with": {}
6198 + }
6199 + },
6200 + "foreignKeys": {},
6201 + "compositePrimaryKeys": {},
6202 + "uniqueConstraints": {},
6203 + "policies": {},
6204 + "checkConstraints": {},
6205 + "isRLSEnabled": false
6206 + },
6207 + "public.search_log": {
6208 + "name": "search_log",
6209 + "schema": "",
6210 + "columns": {
6211 + "id": {
6212 + "name": "id",
6213 + "type": "text",
6214 + "primaryKey": true,
6215 + "notNull": true
6216 + },
6217 + "query": {
6218 + "name": "query",
6219 + "type": "text",
6220 + "primaryKey": false,
6221 + "notNull": true
6222 + },
6223 + "normalized": {
6224 + "name": "normalized",
6225 + "type": "text",
6226 + "primaryKey": false,
6227 + "notNull": true
6228 + },
6229 + "results": {
6230 + "name": "results",
6231 + "type": "integer",
6232 + "primaryKey": false,
6233 + "notNull": true
6234 + },
6235 + "user_id": {
6236 + "name": "user_id",
6237 + "type": "text",
6238 + "primaryKey": false,
6239 + "notNull": false
6240 + },
6241 + "created_at": {
6242 + "name": "created_at",
6243 + "type": "timestamp with time zone",
6244 + "primaryKey": false,
6245 + "notNull": true,
6246 + "default": "now()"
6247 + }
6248 + },
6249 + "indexes": {
6250 + "search_log_created_idx": {
6251 + "name": "search_log_created_idx",
6252 + "columns": [
6253 + {
6254 + "expression": "created_at",
6255 + "isExpression": false,
6256 + "asc": true,
6257 + "nulls": "last"
6258 + }
6259 + ],
6260 + "isUnique": false,
6261 + "concurrently": false,
6262 + "method": "btree",
6263 + "with": {}
6264 + },
6265 + "search_log_normalized_idx": {
6266 + "name": "search_log_normalized_idx",
6267 + "columns": [
6268 + {
6269 + "expression": "normalized",
6270 + "isExpression": false,
6271 + "asc": true,
6272 + "nulls": "last"
6273 + }
6274 + ],
6275 + "isUnique": false,
6276 + "concurrently": false,
6277 + "method": "btree",
6278 + "with": {}
6279 + }
6280 + },
6281 + "foreignKeys": {},
6282 + "compositePrimaryKeys": {},
6283 + "uniqueConstraints": {},
6284 + "policies": {},
6285 + "checkConstraints": {},
6286 + "isRLSEnabled": false
6287 + },
6288 + "public.sessions": {
6289 + "name": "sessions",
6290 + "schema": "",
6291 + "columns": {
6292 + "id": {
6293 + "name": "id",
6294 + "type": "text",
6295 + "primaryKey": true,
6296 + "notNull": true
6297 + },
6298 + "user_id": {
6299 + "name": "user_id",
6300 + "type": "text",
6301 + "primaryKey": false,
6302 + "notNull": true
6303 + },
6304 + "expires_at": {
6305 + "name": "expires_at",
6306 + "type": "timestamp with time zone",
6307 + "primaryKey": false,
6308 + "notNull": true
6309 + },
6310 + "user_agent": {
6311 + "name": "user_agent",
6312 + "type": "text",
6313 + "primaryKey": false,
6314 + "notNull": false
6315 + },
6316 + "created_at": {
6317 + "name": "created_at",
6318 + "type": "timestamp with time zone",
6319 + "primaryKey": false,
6320 + "notNull": true,
6321 + "default": "now()"
6322 + }
6323 + },
6324 + "indexes": {
6325 + "sessions_user_idx": {
6326 + "name": "sessions_user_idx",
6327 + "columns": [
6328 + {
6329 + "expression": "user_id",
6330 + "isExpression": false,
6331 + "asc": true,
6332 + "nulls": "last"
6333 + }
6334 + ],
6335 + "isUnique": false,
6336 + "concurrently": false,
6337 + "method": "btree",
6338 + "with": {}
6339 + }
6340 + },
6341 + "foreignKeys": {},
6342 + "compositePrimaryKeys": {},
6343 + "uniqueConstraints": {},
6344 + "policies": {},
6345 + "checkConstraints": {},
6346 + "isRLSEnabled": false
6347 + },
6348 + "public.users": {
6349 + "name": "users",
6350 + "schema": "",
6351 + "columns": {
6352 + "id": {
6353 + "name": "id",
6354 + "type": "text",
6355 + "primaryKey": true,
6356 + "notNull": true
6357 + },
6358 + "email": {
6359 + "name": "email",
6360 + "type": "text",
6361 + "primaryKey": false,
6362 + "notNull": true
6363 + },
6364 + "email_verified_at": {
6365 + "name": "email_verified_at",
6366 + "type": "timestamp with time zone",
6367 + "primaryKey": false,
6368 + "notNull": false
6369 + },
6370 + "password_hash": {
6371 + "name": "password_hash",
6372 + "type": "text",
6373 + "primaryKey": false,
6374 + "notNull": false
6375 + },
6376 + "name": {
6377 + "name": "name",
6378 + "type": "text",
6379 + "primaryKey": false,
6380 + "notNull": false
6381 + },
6382 + "role": {
6383 + "name": "role",
6384 + "type": "text",
6385 + "primaryKey": false,
6386 + "notNull": true,
6387 + "default": "'user'"
6388 + },
6389 + "display_currency": {
6390 + "name": "display_currency",
6391 + "type": "text",
6392 + "primaryKey": false,
6393 + "notNull": true,
6394 + "default": "'USD'"
6395 + },
6396 + "providers": {
6397 + "name": "providers",
6398 + "type": "jsonb",
6399 + "primaryKey": false,
6400 + "notNull": true,
6401 + "default": "'[]'::jsonb"
6402 + },
6403 + "preferences": {
6404 + "name": "preferences",
6405 + "type": "jsonb",
6406 + "primaryKey": false,
6407 + "notNull": true,
6408 + "default": "'{}'::jsonb"
6409 + },
6410 + "created_at": {
6411 + "name": "created_at",
6412 + "type": "timestamp with time zone",
6413 + "primaryKey": false,
6414 + "notNull": true,
6415 + "default": "now()"
6416 + },
6417 + "last_login_at": {
6418 + "name": "last_login_at",
6419 + "type": "timestamp with time zone",
6420 + "primaryKey": false,
6421 + "notNull": false
6422 + }
6423 + },
6424 + "indexes": {},
6425 + "foreignKeys": {},
6426 + "compositePrimaryKeys": {},
6427 + "uniqueConstraints": {
6428 + "users_email_unique": {
6429 + "name": "users_email_unique",
6430 + "nullsNotDistinct": false,
6431 + "columns": [
6432 + "email"
6433 + ]
6434 + }
6435 + },
6436 + "policies": {},
6437 + "checkConstraints": {},
6438 + "isRLSEnabled": false
6439 + },
6440 + "public.watchlist_items": {
6441 + "name": "watchlist_items",
6442 + "schema": "",
6443 + "columns": {
6444 + "id": {
6445 + "name": "id",
6446 + "type": "text",
6447 + "primaryKey": true,
6448 + "notNull": true
6449 + },
6450 + "watchlist_id": {
6451 + "name": "watchlist_id",
6452 + "type": "text",
6453 + "primaryKey": false,
6454 + "notNull": true
6455 + },
6456 + "target_type": {
6457 + "name": "target_type",
6458 + "type": "text",
6459 + "primaryKey": false,
6460 + "notNull": true
6461 + },
6462 + "target_id": {
6463 + "name": "target_id",
6464 + "type": "text",
6465 + "primaryKey": false,
6466 + "notNull": true
6467 + },
6468 + "created_at": {
6469 + "name": "created_at",
6470 + "type": "timestamp with time zone",
6471 + "primaryKey": false,
6472 + "notNull": true,
6473 + "default": "now()"
6474 + }
6475 + },
6476 + "indexes": {
6477 + "watchlist_items_uq": {
6478 + "name": "watchlist_items_uq",
6479 + "columns": [
6480 + {
6481 + "expression": "watchlist_id",
6482 + "isExpression": false,
6483 + "asc": true,
6484 + "nulls": "last"
6485 + },
6486 + {
6487 + "expression": "target_type",
6488 + "isExpression": false,
6489 + "asc": true,
6490 + "nulls": "last"
6491 + },
6492 + {
6493 + "expression": "target_id",
6494 + "isExpression": false,
6495 + "asc": true,
6496 + "nulls": "last"
6497 + }
6498 + ],
6499 + "isUnique": true,
6500 + "concurrently": false,
6501 + "method": "btree",
6502 + "with": {}
6503 + }
6504 + },
6505 + "foreignKeys": {},
6506 + "compositePrimaryKeys": {},
6507 + "uniqueConstraints": {},
6508 + "policies": {},
6509 + "checkConstraints": {},
6510 + "isRLSEnabled": false
6511 + },
6512 + "public.watchlists": {
6513 + "name": "watchlists",
6514 + "schema": "",
6515 + "columns": {
6516 + "id": {
6517 + "name": "id",
6518 + "type": "text",
6519 + "primaryKey": true,
6520 + "notNull": true
6521 + },
6522 + "user_id": {
6523 + "name": "user_id",
6524 + "type": "text",
6525 + "primaryKey": false,
6526 + "notNull": true
6527 + },
6528 + "name": {
6529 + "name": "name",
6530 + "type": "text",
6531 + "primaryKey": false,
6532 + "notNull": true,
6533 + "default": "'Watchlist'"
6534 + },
6535 + "created_at": {
6536 + "name": "created_at",
6537 + "type": "timestamp with time zone",
6538 + "primaryKey": false,
6539 + "notNull": true,
6540 + "default": "now()"
6541 + }
6542 + },
6543 + "indexes": {
6544 + "watchlists_user_idx": {
6545 + "name": "watchlists_user_idx",
6546 + "columns": [
6547 + {
6548 + "expression": "user_id",
6549 + "isExpression": false,
6550 + "asc": true,
6551 + "nulls": "last"
6552 + }
6553 + ],
6554 + "isUnique": false,
6555 + "concurrently": false,
6556 + "method": "btree",
6557 + "with": {}
6558 + }
6559 + },
6560 + "foreignKeys": {},
6561 + "compositePrimaryKeys": {},
6562 + "uniqueConstraints": {},
6563 + "policies": {},
6564 + "checkConstraints": {},
6565 + "isRLSEnabled": false
6566 + }
6567 + },
6568 + "enums": {},
6569 + "schemas": {},
6570 + "sequences": {},
6571 + "roles": {},
6572 + "policies": {},
6573 + "views": {},
6574 + "_meta": {
6575 + "columns": {},
6576 + "schemas": {},
6577 + "tables": {}
6578 + }
6579 +}
\ No newline at end of file
added packages/database/migrations/meta/_journal.json +13 −0
@@ -0,0 +1,13 @@
1 +{
2 + "version": "7",
3 + "dialect": "postgresql",
4 + "entries": [
5 + {
6 + "idx": 0,
7 + "version": "7",
8 + "when": 1788754246505,
9 + "tag": "0000_clear_unicorn",
10 + "breakpoints": true
11 + }
12 + ]
13 +}
\ No newline at end of file
added packages/database/package.json +38 −0
@@ -0,0 +1,38 @@
1 +{
2 + "name": "@rareindex/database",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + },
11 + "./schema": {
12 + "types": "./src/schema/index.ts",
13 + "default": "./src/schema/index.ts"
14 + }
15 + },
16 + "scripts": {
17 + "build": "tsc -p tsconfig.json --noEmit",
18 + "typecheck": "tsc -p tsconfig.json --noEmit",
19 + "test": "vitest run --passWithNoTests",
20 + "generate": "drizzle-kit generate",
21 + "migrate": "tsx src/migrate.ts",
22 + "seed": "tsx src/seed.ts",
23 + "studio": "drizzle-kit studio"
24 + },
25 + "dependencies": {
26 + "@rareindex/shared": "workspace:*",
27 + "@rareindex/taxonomy": "workspace:*",
28 + "drizzle-orm": "^0.45.0",
29 + "postgres": "^3.4.7"
30 + },
31 + "devDependencies": {
32 + "@types/node": "^24.0.0",
33 + "drizzle-kit": "^0.31.0",
34 + "tsx": "^4.20.0",
35 + "typescript": "^5.9.3",
36 + "vitest": "^3.2.0"
37 + }
38 +}
added packages/database/src/client.ts +44 −0
@@ -0,0 +1,44 @@
1 +import postgres from 'postgres';
2 +import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
3 +import * as schema from './schema/index.js';
4 +
5 +export type Database = PostgresJsDatabase<typeof schema>;
6 +
7 +let _sql: ReturnType<typeof postgres> | null = null;
8 +let _db: Database | null = null;
9 +
10 +export interface DbOptions {
11 + url?: string;
12 + max?: number;
13 +}
14 +
15 +/** Lazily created singleton; safe to import from Next.js server components and workers alike. */
16 +export function getSql(opts: DbOptions = {}) {
17 + if (!_sql) {
18 + const url = opts.url ?? process.env.DATABASE_URL ?? 'postgres://localhost:5432/rareindex';
19 + _sql = postgres(url, {
20 + max: opts.max ?? Number(process.env.DB_POOL_MAX ?? 10),
21 + idle_timeout: 30,
22 + connect_timeout: 10,
23 + prepare: true,
24 + transform: { undefined: null },
25 + onnotice: () => {},
26 + });
27 + }
28 + return _sql;
29 +}
30 +
31 +export function getDb(opts: DbOptions = {}): Database {
32 + if (!_db) _db = drizzle(getSql(opts), { schema, casing: 'snake_case' });
33 + return _db;
34 +}
35 +
36 +export async function closeDb(): Promise<void> {
37 + if (_sql) {
38 + await _sql.end({ timeout: 5 });
39 + _sql = null;
40 + _db = null;
41 + }
42 +}
43 +
44 +export { schema };
added packages/database/src/index.ts +6 −0
@@ -0,0 +1,6 @@
1 +export * from './client.js';
2 +export * as schema from './schema/index.js';
3 +export * from './schema/index.js';
4 +export { runMigrations } from './migrate.js';
5 +export { seedTaxonomy, seedIndices } from './seed.js';
6 +export { sql, eq, and, or, desc, asc, gte, lte, lt, gt, inArray, isNull, isNotNull, ilike, count, sum, avg, max, min, ne, between, notInArray } from 'drizzle-orm';
added packages/database/src/migrate.ts +33 −0
@@ -0,0 +1,33 @@
1 +import { migrate } from 'drizzle-orm/postgres-js/migrator';
2 +import { fileURLToPath } from 'node:url';
3 +import path from 'node:path';
4 +import { getDb, getSql, closeDb } from './client.js';
5 +
6 +/**
7 + * Runs SQL migrations from ./migrations. Extensions are created first (idempotent) because
8 + * drizzle-kit does not manage them; `vector` is optional and only enabled when available.
9 + */
10 +export async function runMigrations(): Promise<void> {
11 + const sql = getSql({ max: 1 });
12 + await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`;
13 + await sql`CREATE EXTENSION IF NOT EXISTS unaccent`;
14 + const [vec] = await sql`SELECT 1 AS ok FROM pg_available_extensions WHERE name = 'vector'`;
15 + if (vec) await sql`CREATE EXTENSION IF NOT EXISTS vector`;
16 + else console.warn('[migrate] pgvector not available: embedding columns will fail — install pgvector');
17 + const here = path.dirname(fileURLToPath(import.meta.url));
18 + await migrate(getDb({ max: 1 }), { migrationsFolder: path.resolve(here, '../migrations') });
19 +}
20 +
21 +const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
22 +if (isMain) {
23 + runMigrations()
24 + .then(async () => {
25 + console.log('[migrate] done');
26 + await closeDb();
27 + })
28 + .catch(async (err) => {
29 + console.error('[migrate] failed', err);
30 + await closeDb();
31 + process.exit(1);
32 + });
33 +}
added packages/database/src/schema/_common.ts +23 −0
@@ -0,0 +1,23 @@
1 +import { customType, numeric, timestamp, text, jsonb } from 'drizzle-orm/pg-core';
2 +import { sql } from 'drizzle-orm';
3 +
4 +/** NUMERIC(18,4) mapped to JS number (values above 2^53 are not expected for collectibles). */
5 +export const money = (name: string) =>
6 + numeric(name, { precision: 18, scale: 4, mode: 'number' });
7 +
8 +export const ratio = (name: string) => numeric(name, { precision: 8, scale: 6, mode: 'number' });
9 +
10 +export const ts = (name: string) => timestamp(name, { withTimezone: true, mode: 'date' });
11 +
12 +export const createdAt = () => ts('created_at').notNull().defaultNow();
13 +export const updatedAt = () => ts('updated_at').notNull().defaultNow();
14 +
15 +export const tsvector = customType<{ data: string }>({
16 + dataType() {
17 + return 'tsvector';
18 + },
19 +});
20 +
21 +export const textArray = (name: string) => text(name).array().notNull().default(sql`'{}'::text[]`);
22 +export const jsonObject = <T extends Record<string, unknown>>(name: string) => jsonb(name).$type<T>().notNull().default(sql`'{}'::jsonb`);
23 +export const jsonArray = <T>(name: string) => jsonb(name).$type<T[]>().notNull().default(sql`'[]'::jsonb`);
added packages/database/src/schema/analytics.ts +168 −0
@@ -0,0 +1,168 @@
1 +import { pgTable, text, integer, boolean, real, index, uniqueIndex, jsonb, date, primaryKey } from 'drizzle-orm/pg-core';
2 +import { createdAt, ts, money, ratio, jsonObject, textArray } from './_common.js';
3 +
4 +/** Full valuation record with method breakdown (§115, §191). */
5 +export const valuations = pgTable(
6 + 'valuations',
7 + {
8 + id: text('id').primaryKey(),
9 + assetId: text('asset_id').notNull(),
10 + variantId: text('variant_id'),
11 + computedAt: ts('computed_at').notNull(),
12 + rivUsd: money('riv_usd'),
13 + lowUsd: money('low_usd'),
14 + highUsd: money('high_usd'),
15 + confidence: ratio('confidence').notNull().default(0),
16 + confidenceLabel: text('confidence_label').notNull().default('insufficient'), // high | medium | low | insufficient
17 + sampleSize: integer('sample_size').notNull().default(0),
18 + windowDays: integer('window_days').notNull().default(365),
19 + methods: jsonObject<Record<string, number | null>>('methods'),
20 + salesUsed: textArray('sales_used'),
21 + observationsUsed: integer('observations_used').notNull().default(0),
22 + method: text('method').notNull().default('ensemble_v1'),
23 + notes: textArray('notes'),
24 + },
25 + (t) => [index('valuations_asset_idx').on(t.assetId, t.computedAt), index('valuations_variant_idx').on(t.variantId, t.computedAt)],
26 +);
27 +
28 +/** Daily price series per asset/variant (repeat-sales friendly; §126 charts). */
29 +export const priceSnapshots = pgTable(
30 + 'price_snapshots',
31 + {
32 + assetId: text('asset_id').notNull(),
33 + variantId: text('variant_id').notNull().default(''),
34 + date: date('date').notNull(),
35 + rivUsd: money('riv_usd'),
36 + latestSaleUsd: money('latest_sale_usd'),
37 + medianUsd: money('median_usd'),
38 + salesCount: integer('sales_count').notNull().default(0),
39 + volumeUsd: money('volume_usd'),
40 + listingsCount: integer('listings_count').notNull().default(0),
41 + minAskUsd: money('min_ask_usd'),
42 + observationUsd: money('observation_usd'),
43 + },
44 + (t) => [primaryKey({ columns: [t.assetId, t.variantId, t.date] }), index('price_snapshots_date_idx').on(t.date)],
45 +);
46 +
47 +/** Index definitions (§3). */
48 +export const indices = pgTable('indices', {
49 + id: text('id').primaryKey(),
50 + ticker: text('ticker').notNull().unique(),
51 + name: text('name').notNull(),
52 + description: text('description'),
53 + categorySlugs: textArray('category_slugs'),
54 + familySlugs: textArray('family_slugs'),
55 + parentTicker: text('parent_ticker'),
56 + methodology: text('methodology').notNull().default('chain_linked_equal_weight_v1'),
57 + weighting: text('weighting').notNull().default('equal'), // equal | liquidity | value
58 + baseDate: date('base_date').notNull(),
59 + baseValue: real('base_value').notNull().default(1000),
60 + minConstituents: integer('min_constituents').notNull().default(10),
61 + active: boolean('active').notNull().default(true),
62 + isFlagship: boolean('is_flagship').notNull().default(false),
63 + color: text('color'),
64 + createdAt: createdAt(),
65 +});
66 +
67 +export const indexValues = pgTable(
68 + 'index_values',
69 + {
70 + indexId: text('index_id').notNull(),
71 + date: date('date').notNull(),
72 + value: real('value').notNull(),
73 + constituentsCount: integer('constituents_count').notNull().default(0),
74 + transactions: integer('transactions').notNull().default(0),
75 + volumeUsd: money('volume_usd'),
76 + medianSaleUsd: money('median_sale_usd'),
77 + avgSaleUsd: money('avg_sale_usd'),
78 + marketCapEstUsd: money('market_cap_est_usd'),
79 + marketCapConfidence: text('market_cap_confidence'),
80 + liquidityScore: real('liquidity_score'),
81 + momentum: real('momentum'),
82 + breadth: integer('breadth'),
83 + trackedAssets: integer('tracked_assets'),
84 + /** fraction of constituents with a fresh valuation that day */
85 + coverage: real('coverage'),
86 + },
87 + (t) => [primaryKey({ columns: [t.indexId, t.date] })],
88 +);
89 +
90 +export const indexConstituents = pgTable(
91 + 'index_constituents',
92 + {
93 + indexId: text('index_id').notNull(),
94 + assetId: text('asset_id').notNull(),
95 + variantId: text('variant_id').notNull().default(''),
96 + weight: real('weight').notNull().default(1),
97 + addedAt: date('added_at').notNull(),
98 + removedAt: date('removed_at'),
99 + reason: text('reason'),
100 + },
101 + (t) => [primaryKey({ columns: [t.indexId, t.assetId, t.variantId, t.addedAt] }), index('index_constituents_asset_idx').on(t.assetId)],
102 +);
103 +
104 +/** Category-level daily aggregates for market pages & heatmap (§125, §151). */
105 +export const categorySnapshots = pgTable(
106 + 'category_snapshots',
107 + {
108 + categorySlug: text('category_slug').notNull(),
109 + date: date('date').notNull(),
110 + indexValue: real('index_value'),
111 + trackedAssets: integer('tracked_assets').notNull().default(0),
112 + assetsWithValuation: integer('assets_with_valuation').notNull().default(0),
113 + sales: integer('sales').notNull().default(0),
114 + volumeUsd: money('volume_usd'),
115 + medianSaleUsd: money('median_sale_usd'),
116 + activeListings: integer('active_listings').notNull().default(0),
117 + marketCapEstUsd: money('market_cap_est_usd'),
118 + liquidityScore: real('liquidity_score'),
119 + change1d: ratio('change_1d'),
120 + change7d: ratio('change_7d'),
121 + change30d: ratio('change_30d'),
122 + change1y: ratio('change_1y'),
123 + },
124 + (t) => [primaryKey({ columns: [t.categorySlug, t.date] })],
125 +);
126 +
127 +/** Rare Radar findings (§155) and record sales (§154) are derived views persisted for speed. */
128 +export const radarFindings = pgTable(
129 + 'radar_findings',
130 + {
131 + id: text('id').primaryKey(),
132 + assetId: text('asset_id').notNull(),
133 + kind: text('kind').notNull(), // first_listing_in_years | ultra_low_population | rare_auction_appearance | price_discrepancy | undocumented_variation | record_sale
134 + score: real('score').notNull(),
135 + evidence: jsonb('evidence').$type<Record<string, unknown>>().notNull().default({}),
136 + entityType: text('entity_type'),
137 + entityId: text('entity_id'),
138 + detectedAt: ts('detected_at').notNull(),
139 + expiresAt: ts('expires_at'),
140 + },
141 + (t) => [index('radar_kind_idx').on(t.kind, t.detectedAt), uniqueIndex('radar_entity_uq').on(t.kind, t.entityType, t.entityId)],
142 +);
143 +
144 +/** Correlation matrix cache (§186). */
145 +export const correlations = pgTable(
146 + 'correlations',
147 + {
148 + a: text('a').notNull(),
149 + b: text('b').notNull(),
150 + windowDays: integer('window_days').notNull(),
151 + coefficient: real('coefficient').notNull(),
152 + observations: integer('observations').notNull(),
153 + computedAt: ts('computed_at').notNull(),
154 + },
155 + (t) => [primaryKey({ columns: [t.a, t.b, t.windowDays] })],
156 +);
157 +
158 +/** Benchmark series (S&P 500, gold, BTC, CPI) fetched from official/free datasets (§187). */
159 +export const benchmarks = pgTable(
160 + 'benchmarks',
161 + {
162 + ticker: text('ticker').notNull(),
163 + date: date('date').notNull(),
164 + value: real('value').notNull(),
165 + source: text('source').notNull(),
166 + },
167 + (t) => [primaryKey({ columns: [t.ticker, t.date] })],
168 +);
added packages/database/src/schema/assets.ts +216 −0
@@ -0,0 +1,216 @@
1 +import { pgTable, text, integer, boolean, real, index, uniqueIndex, jsonb, vector } from 'drizzle-orm/pg-core';
2 +import { sql } from 'drizzle-orm';
3 +import { createdAt, updatedAt, ts, money, ratio, jsonObject, textArray, tsvector } from './_common.js';
4 +
5 +/**
6 + * Canonical asset (§109): the collectible object independent of grade/condition.
7 + * `canonical_key` is a deterministic normalised key (category|set|number|name|variant|language|year…)
8 + * used for entity resolution; `identifiers` hold deterministic external ids.
9 + */
10 +export const assets = pgTable(
11 + 'assets',
12 + {
13 + id: text('id').primaryKey(),
14 + slug: text('slug').notNull(),
15 + canonicalKey: text('canonical_key').notNull(),
16 + categorySlug: text('category_slug').notNull(),
17 + subcategorySlug: text('subcategory_slug'),
18 + familySlug: text('family_slug').notNull(),
19 + franchise: text('franchise'),
20 + brand: text('brand'),
21 + series: text('series'),
22 + setSlug: text('set_slug'),
23 + setName: text('set_name'),
24 + setCode: text('set_code'),
25 + name: text('name').notNull(),
26 + /** full display title, e.g. "Charizard #4/102 — Base Set 1st Edition Holo (1999)" */
27 + title: text('title').notNull(),
28 + model: text('model'),
29 + reference: text('reference'),
30 + number: text('number'),
31 + year: integer('year'),
32 + edition: text('edition'),
33 + variant: text('variant'),
34 + language: text('language'),
35 + region: text('region'),
36 + country: text('country'),
37 + material: text('material'),
38 + size: text('size'),
39 + color: text('color'),
40 + rarity: text('rarity'),
41 + productionQuantity: integer('production_quantity'),
42 + originalMsrp: money('original_msrp'),
43 + originalMsrpCurrency: text('original_msrp_currency'),
44 + releaseDate: text('release_date'),
45 + description: text('description'),
46 + heroImageUrl: text('hero_image_url'),
47 + identifiers: jsonObject<Record<string, string>>('identifiers'),
48 + metadata: jsonObject<Record<string, unknown>>('metadata'),
49 + /** ids of assets merged into this one (audit trail) */
50 + mergedFrom: textArray('merged_from'),
51 + /** 0–100 (§150) */
52 + dataQuality: real('data_quality').notNull().default(0),
53 + verified: boolean('verified').notNull().default(false),
54 + search: tsvector('search').generatedAlwaysAs(
55 + (): ReturnType<typeof sql> =>
56 + sql`setweight(to_tsvector('simple', coalesce(name, '')), 'A') || setweight(to_tsvector('simple', coalesce(set_name, '') || ' ' || coalesce(number, '') || ' ' || coalesce(variant, '') || ' ' || coalesce(edition, '')), 'B') || setweight(to_tsvector('simple', coalesce(brand, '') || ' ' || coalesce(franchise, '') || ' ' || coalesce(reference, '') || ' ' || coalesce(model, '') || ' ' || coalesce(year::text, '') || ' ' || coalesce(category_slug, '')), 'C')`,
57 + ),
58 + createdAt: createdAt(),
59 + updatedAt: updatedAt(),
60 + },
61 + (t) => [
62 + uniqueIndex('assets_canonical_key_uq').on(t.canonicalKey),
63 + uniqueIndex('assets_slug_uq').on(t.slug),
64 + index('assets_category_idx').on(t.categorySlug),
65 + index('assets_family_idx').on(t.familySlug),
66 + index('assets_set_idx').on(t.setSlug),
67 + index('assets_search_gin').using('gin', t.search),
68 + index('assets_title_trgm').using('gin', sql`${t.title} gin_trgm_ops`),
69 + index('assets_identifiers_gin').using('gin', t.identifiers),
70 + ],
71 +);
72 +
73 +/** Grade/condition-specific variant of an asset (PSA 10, CIB, deadstock size 10…) — §112, §117, §118. */
74 +export const assetVariants = pgTable(
75 + 'asset_variants',
76 + {
77 + id: text('id').primaryKey(),
78 + assetId: text('asset_id').notNull(),
79 + variantKey: text('variant_key').notNull(), // e.g. "psa|10", "raw|near_mint", "sealed", "bgs|9.5|black_label", "size|10"
80 + grader: text('grader'),
81 + grade: text('grade'),
82 + qualifier: text('qualifier'),
83 + condition: text('condition'),
84 + completeness: text('completeness'),
85 + sizeLabel: text('size_label'),
86 + label: text('label').notNull(), // human label "PSA 10"
87 + isDefault: boolean('is_default').notNull().default(false),
88 + createdAt: createdAt(),
89 + },
90 + (t) => [uniqueIndex('asset_variants_uq').on(t.assetId, t.variantKey), index('asset_variants_asset_idx').on(t.assetId)],
91 +);
92 +
93 +/** Denormalised, frequently read metrics per asset (rebuilt by valuation/indices workers). */
94 +export const assetStats = pgTable(
95 + 'asset_stats',
96 + {
97 + assetId: text('asset_id').primaryKey(),
98 + rivUsd: money('riv_usd'),
99 + rivLowUsd: money('riv_low_usd'),
100 + rivHighUsd: money('riv_high_usd'),
101 + rivConfidence: ratio('riv_confidence'),
102 + rivSampleSize: integer('riv_sample_size').notNull().default(0),
103 + rivVariantId: text('riv_variant_id'),
104 + latestSaleUsd: money('latest_sale_usd'),
105 + latestSaleAt: ts('latest_sale_at'),
106 + change1d: ratio('change_1d'),
107 + change7d: ratio('change_7d'),
108 + change30d: ratio('change_30d'),
109 + change90d: ratio('change_90d'),
110 + change1y: ratio('change_1y'),
111 + athUsd: money('ath_usd'),
112 + athAt: ts('ath_at'),
113 + atlUsd: money('atl_usd'),
114 + atlAt: ts('atl_at'),
115 + salesCount: integer('sales_count').notNull().default(0),
116 + sales30d: integer('sales_30d').notNull().default(0),
117 + sales1y: integer('sales_1y').notNull().default(0),
118 + volume30dUsd: money('volume_30d_usd'),
119 + activeListings: integer('active_listings').notNull().default(0),
120 + minAskUsd: money('min_ask_usd'),
121 + observationsCount: integer('observations_count').notNull().default(0),
122 + sourcesCount: integer('sources_count').notNull().default(0),
123 + liquidityScore: real('liquidity_score'),
124 + rarityScore: real('rarity_score'),
125 + momentum7d: real('momentum_7d'),
126 + momentum30d: real('momentum_30d'),
127 + momentum90d: real('momentum_90d'),
128 + momentum1y: real('momentum_1y'),
129 + trendingScore: real('trending_score'),
130 + valueOpportunity: real('value_opportunity'),
131 + dataQuality: real('data_quality'),
132 + watchers: integer('watchers').notNull().default(0),
133 + views30d: integer('views_30d').notNull().default(0),
134 + updatedAt: updatedAt(),
135 + },
136 + (t) => [index('asset_stats_riv_idx').on(t.rivUsd), index('asset_stats_trending_idx').on(t.trendingScore), index('asset_stats_liquidity_idx').on(t.liquidityScore)],
137 +);
138 +
139 +export const variantStats = pgTable('variant_stats', {
140 + variantId: text('variant_id').primaryKey(),
141 + assetId: text('asset_id').notNull(),
142 + rivUsd: money('riv_usd'),
143 + rivLowUsd: money('riv_low_usd'),
144 + rivHighUsd: money('riv_high_usd'),
145 + rivConfidence: ratio('riv_confidence'),
146 + rivSampleSize: integer('riv_sample_size').notNull().default(0),
147 + latestSaleUsd: money('latest_sale_usd'),
148 + latestSaleAt: ts('latest_sale_at'),
149 + change30d: ratio('change_30d'),
150 + change1y: ratio('change_1y'),
151 + salesCount: integer('sales_count').notNull().default(0),
152 + sales30d: integer('sales_30d').notNull().default(0),
153 + activeListings: integer('active_listings').notNull().default(0),
154 + minAskUsd: money('min_ask_usd'),
155 + liquidityScore: real('liquidity_score'),
156 + updatedAt: updatedAt(),
157 +});
158 +
159 +export const images = pgTable(
160 + 'images',
161 + {
162 + id: text('id').primaryKey(),
163 + assetId: text('asset_id'),
164 + listingId: text('listing_id'),
165 + saleId: text('sale_id'),
166 + sourceId: text('source_id'),
167 + url: text('url').notNull(),
168 + role: text('role').notNull().default('gallery'), // hero | gallery | source
169 + width: integer('width'),
170 + height: integer('height'),
171 + phash: text('phash'),
172 + embedding: vector('embedding', { dimensions: 512 }),
173 + attribution: text('attribution'),
174 + createdAt: createdAt(),
175 + },
176 + (t) => [index('images_asset_idx').on(t.assetId), index('images_phash_idx').on(t.phash), uniqueIndex('images_url_uq').on(t.url)],
177 +);
178 +
179 +/** Text embeddings for hybrid search / entity resolution (§112, §138). */
180 +export const assetEmbeddings = pgTable('asset_embeddings', {
181 + assetId: text('asset_id').primaryKey(),
182 + model: text('model').notNull(),
183 + embedding: vector('embedding', { dimensions: 1536 }).notNull(),
184 + updatedAt: updatedAt(),
185 +});
186 +
187 +export const populationReports = pgTable(
188 + 'population_reports',
189 + {
190 + id: text('id').primaryKey(),
191 + assetId: text('asset_id').notNull(),
192 + grader: text('grader').notNull(),
193 + sourceId: text('source_id').notNull(),
194 + sourceUrl: text('source_url'),
195 + reportDate: text('report_date').notNull(),
196 + total: integer('total').notNull(),
197 + byGrade: jsonb('by_grade').$type<Record<string, number>>().notNull(),
198 + createdAt: createdAt(),
199 + },
200 + (t) => [uniqueIndex('population_reports_uq').on(t.assetId, t.grader, t.reportDate)],
201 +);
202 +
203 +/** Empirical grade premiums (§118). */
204 +export const gradePremiums = pgTable(
205 + 'grade_premiums',
206 + {
207 + id: text('id').primaryKey(),
208 + categorySlug: text('category_slug').notNull(),
209 + grader: text('grader').notNull(),
210 + grade: text('grade').notNull(),
211 + marketMultiplier: real('market_multiplier').notNull(),
212 + sampleSize: integer('sample_size').notNull(),
213 + computedAt: ts('computed_at').notNull(),
214 + },
215 + (t) => [uniqueIndex('grade_premiums_uq').on(t.categorySlug, t.grader, t.grade)],
216 +);
added packages/database/src/schema/index.ts +7 −0
@@ -0,0 +1,7 @@
1 +export * from './taxonomy.js';
2 +export * from './sources.js';
3 +export * from './ingestion.js';
4 +export * from './assets.js';
5 +export * from './market.js';
6 +export * from './analytics.js';
7 +export * from './users.js';
added packages/database/src/schema/ingestion.ts +94 −0
@@ -0,0 +1,94 @@
1 +import { pgTable, text, integer, index, uniqueIndex, jsonb } from 'drizzle-orm/pg-core';
2 +import { createdAt, ts, ratio } from './_common.js';
3 +
4 +/**
5 + * Immutable raw capture (§108, §142). Rows are never updated except `processed_at`/`process_error`
6 + * bookkeeping columns. Payload is the parsed source payload (JSON from API, or extracted fields);
7 + * large HTML snapshots live in RI_DATA_DIR/raw/<connector>/<hash>.html referenced by snapshot_ref.
8 + */
9 +export const rawRecords = pgTable(
10 + 'raw_records',
11 + {
12 + id: text('id').primaryKey(),
13 + connectorId: text('connector_id').notNull(),
14 + sourceId: text('source_id').notNull(),
15 + runId: text('run_id'),
16 + engine: text('engine').notNull(),
17 + url: text('url').notNull(),
18 + externalId: text('external_id'),
19 + kind: text('kind').notNull(),
20 + fetchedAt: ts('fetched_at').notNull(),
21 + contentHash: text('content_hash').notNull(),
22 + httpStatus: integer('http_status'),
23 + payload: jsonb('payload').notNull(),
24 + snapshotRef: text('snapshot_ref'),
25 + parserVersion: text('parser_version').notNull(),
26 + connectorVersion: text('connector_version').notNull(),
27 + processedAt: ts('processed_at'),
28 + processError: text('process_error'),
29 + createdAt: createdAt(),
30 + },
31 + (t) => [
32 + uniqueIndex('raw_records_connector_hash_uq').on(t.connectorId, t.contentHash),
33 + index('raw_records_connector_fetched_idx').on(t.connectorId, t.fetchedAt),
34 + index('raw_records_unprocessed_idx').on(t.processedAt),
35 + index('raw_records_external_idx').on(t.connectorId, t.externalId),
36 + ],
37 +);
38 +
39 +/** Staging between normalisation and canonical tables; keeps the match decision auditable (§112). */
40 +export const normalizedRecords = pgTable(
41 + 'normalized_records',
42 + {
43 + id: text('id').primaryKey(),
44 + rawRecordId: text('raw_record_id').notNull(),
45 + connectorId: text('connector_id').notNull(),
46 + sourceId: text('source_id').notNull(),
47 + kind: text('kind').notNull(),
48 + payload: jsonb('payload').notNull(),
49 + assetId: text('asset_id'),
50 + variantId: text('variant_id'),
51 + matchMethod: text('match_method'), // identifier | canonical_key | fuzzy | embedding | llm | manual
52 + matchConfidence: ratio('match_confidence'),
53 + status: text('status').notNull().default('pending'), // pending | matched | unmatched | rejected | applied
54 + rejectReason: text('reject_reason'),
55 + targetId: text('target_id'), // resulting sale/listing/observation id
56 + createdAt: createdAt(),
57 + processedAt: ts('processed_at'),
58 + },
59 + (t) => [
60 + index('normalized_records_status_idx').on(t.status, t.createdAt),
61 + index('normalized_records_asset_idx').on(t.assetId),
62 + uniqueIndex('normalized_records_raw_uq').on(t.rawRecordId),
63 + ],
64 +);
65 +
66 +/** Pipeline/event log (§140). Queue itself is pg-boss; this is the durable domain event stream. */
67 +export const events = pgTable(
68 + 'events',
69 + {
70 + id: text('id').primaryKey(),
71 + type: text('type').notNull(), // page_discovered | page_crawled | listing_created | listing_updated | sale_detected | entity_matched | valuation_updated | index_updated
72 + entityType: text('entity_type'),
73 + entityId: text('entity_id'),
74 + payload: jsonb('payload').$type<Record<string, unknown>>().notNull().default({}),
75 + createdAt: createdAt(),
76 + },
77 + (t) => [index('events_type_created_idx').on(t.type, t.createdAt), index('events_entity_idx').on(t.entityType, t.entityId)],
78 +);
79 +
80 +/** Audit log — never silently delete or alter data (§116). */
81 +export const auditLog = pgTable(
82 + 'audit_log',
83 + {
84 + id: text('id').primaryKey(),
85 + entityType: text('entity_type').notNull(),
86 + entityId: text('entity_id').notNull(),
87 + action: text('action').notNull(), // flagged | excluded | restored | merged | edited | rejected
88 + reason: text('reason').notNull(),
89 + actor: text('actor').notNull().default('system'),
90 + details: jsonb('details').$type<Record<string, unknown>>().notNull().default({}),
91 + createdAt: createdAt(),
92 + },
93 + (t) => [index('audit_entity_idx').on(t.entityType, t.entityId)],
94 +);
added packages/database/src/schema/market.ts +230 −0
@@ -0,0 +1,230 @@
1 +import { pgTable, text, integer, boolean, real, index, uniqueIndex, jsonb, date } from 'drizzle-orm/pg-core';
2 +import { createdAt, updatedAt, ts, money, ratio, jsonObject, textArray } from './_common.js';
3 +
4 +/** Verified/observed transactions (§110). Prices keep native currency + USD at historical FX (§136). */
5 +export const sales = pgTable(
6 + 'sales',
7 + {
8 + id: text('id').primaryKey(),
9 + assetId: text('asset_id').notNull(),
10 + variantId: text('variant_id'),
11 + sourceId: text('source_id').notNull(),
12 + connectorId: text('connector_id').notNull(),
13 + rawRecordId: text('raw_record_id'),
14 + normalizedRecordId: text('normalized_record_id'),
15 + sourceUrl: text('source_url').notNull(),
16 + externalId: text('external_id'),
17 + saleType: text('sale_type').notNull().default('unknown'),
18 + saleDate: ts('sale_date').notNull(),
19 + price: money('price').notNull(),
20 + currency: text('currency').notNull(),
21 + priceUsd: money('price_usd').notNull(),
22 + fxRate: real('fx_rate'),
23 + fxDate: date('fx_date'),
24 + buyerPremiumIncluded: boolean('buyer_premium_included'),
25 + quantity: integer('quantity').notNull().default(1),
26 + isBundle: boolean('is_bundle').notNull().default(false),
27 + condition: text('condition'),
28 + grader: text('grader'),
29 + grade: text('grade'),
30 + certificationNumber: text('certification_number'),
31 + location: text('location'),
32 + auctionHouse: text('auction_house'),
33 + lotNumber: text('lot_number'),
34 + imageUrls: jsonb('image_urls').$type<string[]>().notNull().default([]),
35 + rawTitle: text('raw_title').notNull(),
36 + confidence: ratio('confidence').notNull().default(0.8),
37 + dataQuality: real('data_quality').notNull().default(0),
38 + /** valid | flagged | excluded (never deleted, §116) */
39 + status: text('status').notNull().default('valid'),
40 + flags: textArray('flags'),
41 + dedupeKey: text('dedupe_key').notNull(),
42 + createdAt: createdAt(),
43 + },
44 + (t) => [
45 + uniqueIndex('sales_dedupe_uq').on(t.dedupeKey),
46 + index('sales_asset_date_idx').on(t.assetId, t.saleDate),
47 + index('sales_variant_date_idx').on(t.variantId, t.saleDate),
48 + index('sales_source_idx').on(t.sourceId, t.saleDate),
49 + index('sales_date_idx').on(t.saleDate),
50 + index('sales_price_idx').on(t.priceUsd),
51 + ],
52 +);
53 +
54 +/** Current and historical listings (§111). Listing price is never treated as market value. */
55 +export const listings = pgTable(
56 + 'listings',
57 + {
58 + id: text('id').primaryKey(),
59 + assetId: text('asset_id').notNull(),
60 + variantId: text('variant_id'),
61 + sourceId: text('source_id').notNull(),
62 + connectorId: text('connector_id').notNull(),
63 + rawRecordId: text('raw_record_id'),
64 + sourceUrl: text('source_url').notNull(),
65 + externalId: text('external_id').notNull(),
66 + listingType: text('listing_type').notNull().default('unknown'),
67 + price: money('price'),
68 + currency: text('currency'),
69 + priceUsd: money('price_usd'),
70 + seller: text('seller'),
71 + sellerReputation: text('seller_reputation'),
72 + location: text('location'),
73 + shippingCost: money('shipping_cost'),
74 + quantity: integer('quantity'),
75 + condition: text('condition'),
76 + grader: text('grader'),
77 + grade: text('grade'),
78 + certificationNumber: text('certification_number'),
79 + imageUrls: jsonb('image_urls').$type<string[]>().notNull().default([]),
80 + rawTitle: text('raw_title').notNull(),
81 + description: text('description'),
82 + listedAt: ts('listed_at'),
83 + endsAt: ts('ends_at'),
84 + availability: text('availability').notNull().default('available'),
85 + bidCount: integer('bid_count'),
86 + firstSeenAt: ts('first_seen_at').notNull(),
87 + lastSeenAt: ts('last_seen_at').notNull(),
88 + priceChangedAt: ts('price_changed_at'),
89 + crossListingGroupId: text('cross_listing_group_id'),
90 + confidence: ratio('confidence').notNull().default(0.8),
91 + dataQuality: real('data_quality').notNull().default(0),
92 + flags: textArray('flags'),
93 + /** value opportunity vs RIV, computed (§123) */
94 + discountToRiv: ratio('discount_to_riv'),
95 + createdAt: createdAt(),
96 + updatedAt: updatedAt(),
97 + },
98 + (t) => [
99 + uniqueIndex('listings_source_external_uq').on(t.sourceId, t.externalId),
100 + index('listings_asset_avail_idx').on(t.assetId, t.availability),
101 + index('listings_avail_price_idx').on(t.availability, t.priceUsd),
102 + index('listings_ends_idx').on(t.endsAt),
103 + index('listings_last_seen_idx').on(t.lastSeenAt),
104 + ],
105 +);
106 +
107 +export const listingEvents = pgTable(
108 + 'listing_events',
109 + {
110 + id: text('id').primaryKey(),
111 + listingId: text('listing_id').notNull(),
112 + eventType: text('event_type').notNull(), // new | price_changed | sold | removed | relisted | auction_ended
113 + oldPrice: money('old_price'),
114 + newPrice: money('new_price'),
115 + currency: text('currency'),
116 + occurredAt: ts('occurred_at').notNull(),
117 + },
118 + (t) => [index('listing_events_listing_idx').on(t.listingId, t.occurredAt)],
119 +);
120 +
121 +export const auctions = pgTable(
122 + 'auctions',
123 + {
124 + id: text('id').primaryKey(),
125 + sourceId: text('source_id').notNull(),
126 + auctionHouse: text('auction_house').notNull(),
127 + name: text('name').notNull(),
128 + url: text('url').notNull(),
129 + startsAt: ts('starts_at'),
130 + endsAt: ts('ends_at'),
131 + location: text('location'),
132 + categorySlugs: textArray('category_slugs'),
133 + lotCount: integer('lot_count'),
134 + status: text('status').notNull().default('upcoming'), // upcoming | live | ended
135 + currency: text('currency'),
136 + createdAt: createdAt(),
137 + updatedAt: updatedAt(),
138 + },
139 + (t) => [uniqueIndex('auctions_url_uq').on(t.url), index('auctions_ends_idx').on(t.endsAt)],
140 +);
141 +
142 +export const auctionLots = pgTable(
143 + 'auction_lots',
144 + {
145 + id: text('id').primaryKey(),
146 + auctionId: text('auction_id').notNull(),
147 + assetId: text('asset_id'),
148 + variantId: text('variant_id'),
149 + sourceId: text('source_id').notNull(),
150 + lotNumber: text('lot_number'),
151 + title: text('title').notNull(),
152 + url: text('url').notNull(),
153 + estimateLow: money('estimate_low'),
154 + estimateHigh: money('estimate_high'),
155 + currentBid: money('current_bid'),
156 + hammerPrice: money('hammer_price'),
157 + currency: text('currency'),
158 + bidCount: integer('bid_count'),
159 + startsAt: ts('starts_at'),
160 + endsAt: ts('ends_at'),
161 + status: text('status').notNull().default('upcoming'),
162 + imageUrls: jsonb('image_urls').$type<string[]>().notNull().default([]),
163 + grader: text('grader'),
164 + grade: text('grade'),
165 + createdAt: createdAt(),
166 + updatedAt: updatedAt(),
167 + },
168 + (t) => [uniqueIndex('auction_lots_url_uq').on(t.url), index('auction_lots_auction_idx').on(t.auctionId), index('auction_lots_asset_idx').on(t.assetId), index('auction_lots_ends_idx').on(t.endsAt)],
169 +);
170 +
171 +/** Price-guide observations (market/low/mid/high) — informative, weighted below transactions. */
172 +export const priceObservations = pgTable(
173 + 'price_observations',
174 + {
175 + id: text('id').primaryKey(),
176 + assetId: text('asset_id').notNull(),
177 + variantId: text('variant_id'),
178 + sourceId: text('source_id').notNull(),
179 + connectorId: text('connector_id').notNull(),
180 + rawRecordId: text('raw_record_id'),
181 + sourceUrl: text('source_url').notNull(),
182 + priceKind: text('price_kind').notNull(),
183 + price: money('price').notNull(),
184 + currency: text('currency').notNull(),
185 + priceUsd: money('price_usd').notNull(),
186 + observationDate: date('observation_date').notNull(),
187 + sampleSize: integer('sample_size'),
188 + dedupeKey: text('dedupe_key').notNull(),
189 + createdAt: createdAt(),
190 + },
191 + (t) => [uniqueIndex('price_observations_dedupe_uq').on(t.dedupeKey), index('price_observations_asset_date_idx').on(t.assetId, t.observationDate)],
192 +);
193 +
194 +export const crossListingGroups = pgTable('cross_listing_groups', {
195 + id: text('id').primaryKey(),
196 + assetId: text('asset_id'),
197 + signals: jsonObject<Record<string, unknown>>('signals'),
198 + createdAt: createdAt(),
199 +});
200 +
201 +/** Daily FX rates from an official dataset (ECB via frankfurter); base USD (§136). */
202 +export const fxRates = pgTable(
203 + 'fx_rates',
204 + {
205 + date: date('date').notNull(),
206 + base: text('base').notNull(),
207 + quote: text('quote').notNull(),
208 + rate: real('rate').notNull(),
209 + source: text('source').notNull().default('ecb'),
210 + },
211 + (t) => [uniqueIndex('fx_rates_uq').on(t.date, t.base, t.quote)],
212 +);
213 +
214 +export const news = pgTable(
215 + 'news',
216 + {
217 + id: text('id').primaryKey(),
218 + sourceId: text('source_id').notNull(),
219 + url: text('url').notNull(),
220 + title: text('title').notNull(),
221 + summary: text('summary'),
222 + aiSummary: text('ai_summary'),
223 + publishedAt: ts('published_at'),
224 + categorySlugs: textArray('category_slugs'),
225 + newsType: text('news_type'), // auction_results | record_sales | grading | releases | trends | discoveries | events
226 + imageUrl: text('image_url'),
227 + fetchedAt: ts('fetched_at').notNull(),
228 + },
229 + (t) => [uniqueIndex('news_url_uq').on(t.url), index('news_published_idx').on(t.publishedAt)],
230 +);
added packages/database/src/schema/sources.ts +127 −0
@@ -0,0 +1,127 @@
1 +import { pgTable, text, integer, boolean, real, index, jsonb } from 'drizzle-orm/pg-core';
2 +import { createdAt, updatedAt, ts, jsonObject, textArray, ratio } from './_common.js';
3 +
4 +/** A data source (marketplace, auction house, price guide…) — §107, §143. */
5 +export const sources = pgTable('sources', {
6 + id: text('id').primaryKey(), // slug-like, e.g. "scryfall", "psa"
7 + name: text('name').notNull(),
8 + sourceType: text('source_type').notNull(),
9 + url: text('url'),
10 + countries: textArray('countries'),
11 + languages: textArray('languages'),
12 + currencies: textArray('currencies'),
13 + /** 0–1 (§143) */
14 + trustScore: ratio('trust_score').notNull().default(0.5),
15 + trustFactors: jsonObject<Record<string, number>>('trust_factors'),
16 + attributionRequired: boolean('attribution_required').notNull().default(true),
17 + termsUrl: text('terms_url'),
18 + active: boolean('active').notNull().default(true),
19 + createdAt: createdAt(),
20 + updatedAt: updatedAt(),
21 +});
22 +
23 +/** Connector registry mirror (connectors/registry.json is the source of truth for metadata; DB holds state). */
24 +export const connectors = pgTable('connectors', {
25 + id: text('id').primaryKey(),
26 + sourceId: text('source_id').notNull(),
27 + displayName: text('display_name').notNull(),
28 + enginePriority: textArray('engine_priority'),
29 + categories: textArray('categories'),
30 + regions: textArray('regions'),
31 + languages: textArray('languages'),
32 + currency: textArray('currency'),
33 + supportsListings: boolean('supports_listings').notNull().default(false),
34 + supportsSold: boolean('supports_sold').notNull().default(false),
35 + supportsAuctions: boolean('supports_auctions').notNull().default(false),
36 + supportsImages: boolean('supports_images').notNull().default(true),
37 + supportsCatalog: boolean('supports_catalog').notNull().default(false),
38 + supportsPopulation: boolean('supports_population').notNull().default(false),
39 + refreshFrequencyMinutes: integer('refresh_frequency_minutes').notNull().default(1440),
40 + priority: text('priority').notNull().default('medium'), // high | medium | low (§141)
41 + status: text('status').notNull().default('active'), // active | paused | disabled
42 + schemaVersion: text('schema_version').notNull().default('1.0'),
43 + connectorVersion: text('connector_version').notNull().default('1.0.0'),
44 + config: jsonObject<Record<string, unknown>>('config'),
45 + lastRunAt: ts('last_run_at'),
46 + lastSuccessAt: ts('last_success_at'),
47 + nextRunAt: ts('next_run_at'),
48 + createdAt: createdAt(),
49 + updatedAt: updatedAt(),
50 +});
51 +
52 +export const connectorRuns = pgTable(
53 + 'connector_runs',
54 + {
55 + id: text('id').primaryKey(),
56 + connectorId: text('connector_id').notNull(),
57 + trigger: text('trigger').notNull().default('schedule'), // schedule | manual | backfill | repair
58 + startedAt: ts('started_at').notNull(),
59 + finishedAt: ts('finished_at'),
60 + status: text('status').notNull().default('running'), // running | success | partial | failed
61 + pagesAttempted: integer('pages_attempted').notNull().default(0),
62 + pagesSuccess: integer('pages_success').notNull().default(0),
63 + recordsRaw: integer('records_raw').notNull().default(0),
64 + recordsNormalized: integer('records_normalized').notNull().default(0),
65 + recordsDuplicate: integer('records_duplicate').notNull().default(0),
66 + recordsRejected: integer('records_rejected').notNull().default(0),
67 + /** per engine: {firecrawl:{attempts,success,credits}, scrapfly:{…}} */
68 + engineStats: jsonObject<Record<string, { attempts: number; success: number; credits: number; ms: number }>>('engine_stats'),
69 + anomalies: jsonb('anomalies').$type<string[]>().notNull().default([]),
70 + error: text('error'),
71 + costCredits: real('cost_credits').notNull().default(0),
72 + costUsdEst: real('cost_usd_est').notNull().default(0),
73 + cursor: jsonObject<Record<string, unknown>>('cursor'),
74 + },
75 + (t) => [index('connector_runs_connector_started_idx').on(t.connectorId, t.startedAt)],
76 +);
77 +
78 +/** Latest computed health snapshot per connector (§105). */
79 +export const connectorHealth = pgTable('connector_health', {
80 + connectorId: text('connector_id').primaryKey(),
81 + computedAt: ts('computed_at').notNull(),
82 + status: text('status').notNull(),
83 + health: jsonObject<Record<string, unknown>>('health'),
84 +});
85 +
86 +/** Per-URL crawl budget state (§170). */
87 +export const crawlState = pgTable(
88 + 'crawl_state',
89 + {
90 + urlHash: text('url_hash').primaryKey(),
91 + connectorId: text('connector_id').notNull(),
92 + url: text('url').notNull(),
93 + etag: text('etag'),
94 + lastModified: text('last_modified'),
95 + contentHash: text('content_hash'),
96 + lastFetchedAt: ts('last_fetched_at'),
97 + lastChangedAt: ts('last_changed_at'),
98 + fetchCount: integer('fetch_count').notNull().default(0),
99 + changeCount: integer('change_count').notNull().default(0),
100 + /** estimated hours between changes */
101 + changeIntervalHours: real('change_interval_hours'),
102 + nextFetchAt: ts('next_fetch_at'),
103 + lastStatus: integer('last_status'),
104 + failures: integer('failures').notNull().default(0),
105 + },
106 + (t) => [index('crawl_state_next_idx').on(t.connectorId, t.nextFetchAt)],
107 +);
108 +
109 +/** Cost ledger for crawlers and AI (§169). */
110 +export const costs = pgTable(
111 + 'costs',
112 + {
113 + id: text('id').primaryKey(),
114 + occurredAt: ts('occurred_at').notNull(),
115 + kind: text('kind').notNull(), // firecrawl | scrapfly | ai | storage
116 + provider: text('provider'),
117 + connectorId: text('connector_id'),
118 + categorySlug: text('category_slug'),
119 + endpoint: text('endpoint'),
120 + userId: text('user_id'),
121 + units: real('units').notNull().default(1),
122 + credits: real('credits').notNull().default(0),
123 + usdEst: real('usd_est').notNull().default(0),
124 + metadata: jsonObject<Record<string, unknown>>('metadata'),
125 + },
126 + (t) => [index('costs_occurred_idx').on(t.occurredAt), index('costs_connector_idx').on(t.connectorId)],
127 +);
added packages/database/src/schema/taxonomy.ts +106 −0
@@ -0,0 +1,106 @@
1 +import { pgTable, text, integer, boolean, index, uniqueIndex } from 'drizzle-orm/pg-core';
2 +import { createdAt, updatedAt, jsonObject, textArray } from './_common.js';
3 +
4 +/**
5 + * Extensible taxonomy (CLAUDE.md §4, §101). Rows are seeded from data/taxonomy/*.json and can be
6 + * extended at runtime through approved taxonomy_proposals. Never hardcode categories in app code.
7 + */
8 +export const categories = pgTable(
9 + 'categories',
10 + {
11 + slug: text('slug').primaryKey(),
12 + parentSlug: text('parent_slug'),
13 + /** top-level family slug (e.g. trading_cards) for fast grouping */
14 + familySlug: text('family_slug').notNull(),
15 + name: text('name').notNull(),
16 + shortName: text('short_name'),
17 + description: text('description'),
18 + level: integer('level').notNull().default(0),
19 + /** 1 = launch wedge, 2, 3 = later phases (§180–182) */
20 + phase: integer('phase').notNull().default(3),
21 + active: boolean('active').notNull().default(true),
22 + /** slug of the condition scale in data/taxonomy/conditions.json */
23 + conditionScale: text('condition_scale'),
24 + /** grader slugs relevant to this category */
25 + graders: textArray('graders'),
26 + /** subindex ticker this category rolls into (e.g. RARE-TCG) */
27 + indexTicker: text('index_ticker'),
28 + /** JSON schema fragment describing category-specific attributes */
29 + attributeSchema: jsonObject<Record<string, unknown>>('attribute_schema'),
30 + sortOrder: integer('sort_order').notNull().default(0),
31 + /** compliance flags: e.g. ["jurisdiction_check", "neutral_presentation"] */
32 + complianceFlags: textArray('compliance_flags'),
33 + icon: text('icon'),
34 + createdAt: createdAt(),
35 + updatedAt: updatedAt(),
36 + },
37 + (t) => [index('categories_parent_idx').on(t.parentSlug), index('categories_family_idx').on(t.familySlug)],
38 +);
39 +
40 +export const brands = pgTable(
41 + 'brands',
42 + {
43 + slug: text('slug').primaryKey(),
44 + name: text('name').notNull(),
45 + categorySlugs: textArray('category_slugs'),
46 + country: text('country'),
47 + foundedYear: integer('founded_year'),
48 + metadata: jsonObject<Record<string, unknown>>('metadata'),
49 + createdAt: createdAt(),
50 + },
51 + (t) => [index('brands_name_idx').on(t.name)],
52 +);
53 +
54 +export const franchises = pgTable('franchises', {
55 + slug: text('slug').primaryKey(),
56 + name: text('name').notNull(),
57 + owner: text('owner'),
58 + categorySlugs: textArray('category_slugs'),
59 + metadata: jsonObject<Record<string, unknown>>('metadata'),
60 + createdAt: createdAt(),
61 +});
62 +
63 +/** Sets / series / releases (Base Set, Alpha, Series 1 Prizm, LEGO theme year…). */
64 +export const sets = pgTable(
65 + 'sets',
66 + {
67 + slug: text('slug').primaryKey(),
68 + name: text('name').notNull(),
69 + code: text('code'),
70 + categorySlug: text('category_slug').notNull(),
71 + franchiseSlug: text('franchise_slug'),
72 + brandSlug: text('brand_slug'),
73 + releaseYear: integer('release_year'),
74 + releaseDate: text('release_date'),
75 + language: text('language'),
76 + totalItems: integer('total_items'),
77 + identifiers: jsonObject<Record<string, string>>('identifiers'),
78 + metadata: jsonObject<Record<string, unknown>>('metadata'),
79 + createdAt: createdAt(),
80 + },
81 + (t) => [index('sets_category_idx').on(t.categorySlug), uniqueIndex('sets_category_code_uq').on(t.categorySlug, t.code)],
82 +);
83 +
84 +export const graders = pgTable('graders', {
85 + slug: text('slug').primaryKey(), // psa, bgs, cgc, sgc, tag, wata, vga, pcgs, ngc, cbcs, anacs, iccs
86 + name: text('name').notNull(),
87 + categorySlugs: textArray('category_slugs'),
88 + /** ordered scale values e.g. ["1","1.5",…,"10"] */
89 + scale: jsonObject<{ values: string[]; top: string; type: 'numeric' | 'sheldon' | 'descriptive' }>('scale'),
90 + populationUrl: text('population_url'),
91 + verifyUrl: text('verify_url'),
92 + active: boolean('active').notNull().default(true),
93 +});
94 +
95 +export const taxonomyProposals = pgTable('taxonomy_proposals', {
96 + id: text('id').primaryKey(),
97 + proposedSlug: text('proposed_slug').notNull(),
98 + name: text('name').notNull(),
99 + parentSlug: text('parent_slug'),
100 + evidence: jsonObject<Record<string, unknown>>('evidence'),
101 + volumeEstimate: integer('volume_estimate'),
102 + status: text('status').notNull().default('pending'), // pending | approved | rejected
103 + decidedBy: text('decided_by'),
104 + decidedAt: text('decided_at'),
105 + createdAt: createdAt(),
106 +});
added packages/database/src/schema/users.ts +191 −0
@@ -0,0 +1,191 @@
1 +import { pgTable, text, integer, boolean, real, index, uniqueIndex, jsonb, date, primaryKey } from 'drizzle-orm/pg-core';
2 +import { createdAt, updatedAt, ts, money, jsonObject } from './_common.js';
3 +
4 +export const users = pgTable('users', {
5 + id: text('id').primaryKey(),
6 + email: text('email').notNull().unique(),
7 + emailVerifiedAt: ts('email_verified_at'),
8 + passwordHash: text('password_hash'),
9 + name: text('name'),
10 + role: text('role').notNull().default('user'), // user | pro | admin
11 + displayCurrency: text('display_currency').notNull().default('USD'),
12 + /** oauth/passkey providers linked (§175) */
13 + providers: jsonb('providers').$type<Array<{ provider: string; subject: string }>>().notNull().default([]),
14 + preferences: jsonObject<Record<string, unknown>>('preferences'),
15 + createdAt: createdAt(),
16 + lastLoginAt: ts('last_login_at'),
17 +});
18 +
19 +export const sessions = pgTable(
20 + 'sessions',
21 + {
22 + id: text('id').primaryKey(),
23 + userId: text('user_id').notNull(),
24 + expiresAt: ts('expires_at').notNull(),
25 + userAgent: text('user_agent'),
26 + createdAt: createdAt(),
27 + },
28 + (t) => [index('sessions_user_idx').on(t.userId)],
29 +);
30 +
31 +export const collections = pgTable(
32 + 'collections',
33 + {
34 + id: text('id').primaryKey(),
35 + userId: text('user_id').notNull(),
36 + name: text('name').notNull(),
37 + description: text('description'),
38 + isPublic: boolean('is_public').notNull().default(false),
39 + publicSlug: text('public_slug'),
40 + createdAt: createdAt(),
41 + updatedAt: updatedAt(),
42 + },
43 + (t) => [index('collections_user_idx').on(t.userId), uniqueIndex('collections_public_slug_uq').on(t.publicSlug)],
44 +);
45 +
46 +export const collectionItems = pgTable(
47 + 'collection_items',
48 + {
49 + id: text('id').primaryKey(),
50 + collectionId: text('collection_id').notNull(),
51 + assetId: text('asset_id').notNull(),
52 + variantId: text('variant_id'),
53 + quantity: integer('quantity').notNull().default(1),
54 + acquiredAt: date('acquired_at'),
55 + purchasePrice: money('purchase_price'),
56 + purchaseCurrency: text('purchase_currency'),
57 + purchasePriceUsd: money('purchase_price_usd'),
58 + source: text('source'),
59 + grader: text('grader'),
60 + grade: text('grade'),
61 + certificationNumber: text('certification_number'),
62 + serial: text('serial'),
63 + photos: jsonb('photos').$type<string[]>().notNull().default([]),
64 + notes: text('notes'),
65 + createdAt: createdAt(),
66 + updatedAt: updatedAt(),
67 + },
68 + (t) => [index('collection_items_collection_idx').on(t.collectionId), index('collection_items_asset_idx').on(t.assetId)],
69 +);
70 +
71 +/** Daily portfolio value history (§130). */
72 +export const collectionSnapshots = pgTable(
73 + 'collection_snapshots',
74 + {
75 + collectionId: text('collection_id').notNull(),
76 + date: date('date').notNull(),
77 + valueUsd: money('value_usd').notNull(),
78 + costBasisUsd: money('cost_basis_usd').notNull(),
79 + items: integer('items').notNull(),
80 + },
81 + (t) => [primaryKey({ columns: [t.collectionId, t.date] })],
82 +);
83 +
84 +export const watchlists = pgTable(
85 + 'watchlists',
86 + {
87 + id: text('id').primaryKey(),
88 + userId: text('user_id').notNull(),
89 + name: text('name').notNull().default('Watchlist'),
90 + createdAt: createdAt(),
91 + },
92 + (t) => [index('watchlists_user_idx').on(t.userId)],
93 +);
94 +
95 +export const watchlistItems = pgTable(
96 + 'watchlist_items',
97 + {
98 + id: text('id').primaryKey(),
99 + watchlistId: text('watchlist_id').notNull(),
100 + targetType: text('target_type').notNull(), // asset | category | brand | set | source | auction
101 + targetId: text('target_id').notNull(),
102 + createdAt: createdAt(),
103 + },
104 + (t) => [uniqueIndex('watchlist_items_uq').on(t.watchlistId, t.targetType, t.targetId)],
105 +);
106 +
107 +export const alerts = pgTable(
108 + 'alerts',
109 + {
110 + id: text('id').primaryKey(),
111 + userId: text('user_id').notNull(),
112 + alertType: text('alert_type').notNull(), // price_below | price_above | new_listing | new_auction | auction_ending | record_sale | unusual_volume | market_move | rare_item | population_update
113 + targetType: text('target_type').notNull(),
114 + targetId: text('target_id').notNull(),
115 + threshold: money('threshold'),
116 + currency: text('currency'),
117 + params: jsonObject<Record<string, unknown>>('params'),
118 + channel: text('channel').notNull().default('inapp'), // inapp | email
119 + active: boolean('active').notNull().default(true),
120 + lastTriggeredAt: ts('last_triggered_at'),
121 + createdAt: createdAt(),
122 + },
123 + (t) => [index('alerts_user_idx').on(t.userId), index('alerts_target_idx').on(t.targetType, t.targetId, t.active)],
124 +);
125 +
126 +export const alertEvents = pgTable(
127 + 'alert_events',
128 + {
129 + id: text('id').primaryKey(),
130 + alertId: text('alert_id').notNull(),
131 + userId: text('user_id').notNull(),
132 + message: text('message').notNull(),
133 + payload: jsonObject<Record<string, unknown>>('payload'),
134 + readAt: ts('read_at'),
135 + createdAt: createdAt(),
136 + },
137 + (t) => [index('alert_events_user_idx').on(t.userId, t.createdAt)],
138 +);
139 +
140 +export const apiKeys = pgTable(
141 + 'api_keys',
142 + {
143 + id: text('id').primaryKey(),
144 + userId: text('user_id'),
145 + name: text('name').notNull(),
146 + prefix: text('prefix').notNull(),
147 + keyHash: text('key_hash').notNull().unique(),
148 + tier: text('tier').notNull().default('free'), // free | hobby | professional | research | enterprise
149 + rateLimitPerMinute: integer('rate_limit_per_minute').notNull().default(60),
150 + dailyQuota: integer('daily_quota').notNull().default(1000),
151 + createdAt: createdAt(),
152 + lastUsedAt: ts('last_used_at'),
153 + revokedAt: ts('revoked_at'),
154 + },
155 + (t) => [index('api_keys_user_idx').on(t.userId)],
156 +);
157 +
158 +export const apiUsage = pgTable(
159 + 'api_usage',
160 + {
161 + keyId: text('key_id').notNull(),
162 + date: date('date').notNull(),
163 + endpoint: text('endpoint').notNull(),
164 + count: integer('count').notNull().default(0),
165 + latencyMsAvg: real('latency_ms_avg'),
166 + },
167 + (t) => [primaryKey({ columns: [t.keyId, t.date, t.endpoint] })],
168 +);
169 +
170 +export const assetViews = pgTable(
171 + 'asset_views',
172 + {
173 + assetId: text('asset_id').notNull(),
174 + date: date('date').notNull(),
175 + views: integer('views').notNull().default(0),
176 + },
177 + (t) => [primaryKey({ columns: [t.assetId, t.date] })],
178 +);
179 +
180 +export const searchLog = pgTable(
181 + 'search_log',
182 + {
183 + id: text('id').primaryKey(),
184 + query: text('query').notNull(),
185 + normalized: text('normalized').notNull(),
186 + results: integer('results').notNull(),
187 + userId: text('user_id'),
188 + createdAt: createdAt(),
189 + },
190 + (t) => [index('search_log_created_idx').on(t.createdAt), index('search_log_normalized_idx').on(t.normalized)],
191 +);
added packages/database/src/seed.ts +166 −0
@@ -0,0 +1,166 @@
1 +import { fileURLToPath } from 'node:url';
2 +import path from 'node:path';
3 +import { readFileSync, existsSync } from 'node:fs';
4 +import { sql } from 'drizzle-orm';
5 +import { CATEGORIES, GRADERS, INDICES, INDEX_BASE_DATE, INDEX_BASE_VALUE } from '@rareindex/taxonomy';
6 +import { getDb, closeDb } from './client.js';
7 +import { categories, graders, indices, sources, connectors } from './schema/index.js';
8 +
9 +/** Upsert taxonomy nodes + graders from data/taxonomy (idempotent). */
10 +export async function seedTaxonomy(): Promise<void> {
11 + const db = getDb();
12 + for (const c of CATEGORIES) {
13 + await db
14 + .insert(categories)
15 + .values({
16 + slug: c.slug,
17 + parentSlug: c.parent ?? null,
18 + familySlug: c.familySlug,
19 + name: c.name,
20 + shortName: c.short ?? null,
21 + description: c.description ?? null,
22 + level: c.level,
23 + phase: c.phase,
24 + conditionScale: c.conditionScale ?? 'general',
25 + graders: c.graders ?? [],
26 + indexTicker: c.index ?? null,
27 + sortOrder: c.sortOrder,
28 + complianceFlags: c.compliance ?? [],
29 + icon: c.icon ?? null,
30 + })
31 + .onConflictDoUpdate({
32 + target: categories.slug,
33 + set: {
34 + parentSlug: c.parent ?? null,
35 + familySlug: c.familySlug,
36 + name: c.name,
37 + shortName: c.short ?? null,
38 + description: c.description ?? null,
39 + level: c.level,
40 + phase: c.phase,
41 + conditionScale: c.conditionScale ?? 'general',
42 + graders: c.graders ?? [],
43 + indexTicker: c.index ?? null,
44 + sortOrder: c.sortOrder,
45 + complianceFlags: c.compliance ?? [],
46 + icon: c.icon ?? null,
47 + updatedAt: new Date(),
48 + },
49 + });
50 + }
51 + for (const g of GRADERS) {
52 + await db
53 + .insert(graders)
54 + .values({ slug: g.slug, name: g.name, categorySlugs: g.categories, scale: g.scale, populationUrl: g.populationUrl ?? null, verifyUrl: g.verifyUrl ?? null })
55 + .onConflictDoUpdate({ target: graders.slug, set: { name: g.name, categorySlugs: g.categories, scale: g.scale, populationUrl: g.populationUrl ?? null, verifyUrl: g.verifyUrl ?? null } });
56 + }
57 +}
58 +
59 +/** Upsert index definitions (§3). */
60 +export async function seedIndices(): Promise<void> {
61 + const db = getDb();
62 + for (const i of INDICES) {
63 + const familySlugs = i.families;
64 + const categorySlugs = CATEGORIES.filter((c) => familySlugs.includes(c.familySlug)).map((c) => c.slug);
65 + await db
66 + .insert(indices)
67 + .values({
68 + id: `idx_${i.ticker.toLowerCase().replace(/[^a-z0-9]+/g, '_')}`,
69 + ticker: i.ticker,
70 + name: i.name,
71 + description: i.description ?? null,
72 + categorySlugs,
73 + familySlugs,
74 + parentTicker: i.flagship ? null : 'RARE',
75 + baseDate: INDEX_BASE_DATE,
76 + baseValue: INDEX_BASE_VALUE,
77 + minConstituents: i.minConstituents,
78 + isFlagship: Boolean(i.flagship),
79 + color: i.color,
80 + })
81 + .onConflictDoUpdate({ target: indices.ticker, set: { name: i.name, description: i.description ?? null, categorySlugs, familySlugs, minConstituents: i.minConstituents, color: i.color } });
82 + }
83 +}
84 +
85 +/** Mirror connectors/registry.json into sources + connectors tables (state columns are preserved). */
86 +export async function seedRegistry(): Promise<void> {
87 + const here = path.dirname(fileURLToPath(import.meta.url));
88 + const registryPath = path.resolve(here, '../../../connectors/registry.json');
89 + if (!existsSync(registryPath)) return;
90 + const reg = JSON.parse(readFileSync(registryPath, 'utf8')) as { connectors: Array<Record<string, any>> };
91 + const db = getDb();
92 + for (const c of reg.connectors) {
93 + await db
94 + .insert(sources)
95 + .values({ id: c.sourceId, name: c.sourceName, sourceType: c.sourceType, url: c.sourceUrl, countries: c.regions ?? [], languages: c.languages ?? ['en'], currencies: c.currency ?? ['USD'], trustScore: c.trustScore ?? 0.6, attributionRequired: c.attributionRequired ?? true, termsUrl: c.termsUrl ?? null })
96 + .onConflictDoUpdate({ target: sources.id, set: { name: c.sourceName, sourceType: c.sourceType, url: c.sourceUrl, trustScore: c.trustScore ?? 0.6, termsUrl: c.termsUrl ?? null, updatedAt: new Date() } });
97 + await db
98 + .insert(connectors)
99 + .values({
100 + id: c.id,
101 + sourceId: c.sourceId,
102 + displayName: c.displayName,
103 + enginePriority: c.enginePriority,
104 + categories: c.categories,
105 + regions: c.regions ?? [],
106 + languages: c.languages ?? ['en'],
107 + currency: c.currency ?? ['USD'],
108 + supportsListings: c.supportsListings ?? false,
109 + supportsSold: c.supportsSold ?? false,
110 + supportsAuctions: c.supportsAuctions ?? false,
111 + supportsImages: c.supportsImages ?? true,
112 + supportsCatalog: c.supportsCatalog ?? false,
113 + supportsPopulation: c.supportsPopulation ?? false,
114 + refreshFrequencyMinutes: c.refreshFrequencyMinutes ?? 1440,
115 + priority: c.priority ?? 'medium',
116 + status: c.enabled === false ? 'disabled' : 'active',
117 + schemaVersion: c.schemaVersion ?? '1.0',
118 + config: c.config ?? {},
119 + })
120 + .onConflictDoUpdate({
121 + target: connectors.id,
122 + set: {
123 + sourceId: c.sourceId,
124 + displayName: c.displayName,
125 + enginePriority: c.enginePriority,
126 + categories: c.categories,
127 + regions: c.regions ?? [],
128 + languages: c.languages ?? ['en'],
129 + currency: c.currency ?? ['USD'],
130 + supportsListings: c.supportsListings ?? false,
131 + supportsSold: c.supportsSold ?? false,
132 + supportsAuctions: c.supportsAuctions ?? false,
133 + supportsImages: c.supportsImages ?? true,
134 + supportsCatalog: c.supportsCatalog ?? false,
135 + supportsPopulation: c.supportsPopulation ?? false,
136 + refreshFrequencyMinutes: c.refreshFrequencyMinutes ?? 1440,
137 + priority: c.priority ?? 'medium',
138 + schemaVersion: c.schemaVersion ?? '1.0',
139 + config: c.config ?? {},
140 + updatedAt: new Date(),
141 + // status is intentionally NOT overwritten (admin pause survives reseeds) except when disabled in registry
142 + ...(c.enabled === false ? { status: 'disabled' } : {}),
143 + },
144 + });
145 + }
146 +}
147 +
148 +export async function seedAll(): Promise<void> {
149 + await seedTaxonomy();
150 + await seedIndices();
151 + await seedRegistry();
152 + const db = getDb();
153 + const [{ n }] = (await db.execute(sql`select count(*)::int as n from categories`)) as unknown as [{ n: number }];
154 + console.log(`[seed] categories=${n} indices=${INDICES.length}`);
155 +}
156 +
157 +const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
158 +if (isMain) {
159 + seedAll()
160 + .then(() => closeDb())
161 + .catch(async (e) => {
162 + console.error(e);
163 + await closeDb();
164 + process.exit(1);
165 + });
166 +}
added packages/database/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": "src",
5 + "outDir": "dist",
6 + "noEmit": true
7 + },
8 + "include": ["src"]
9 +}
added packages/database/vitest.config.ts +5 −0
@@ -0,0 +1,5 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: { include: ['src/**/*.test.ts'] },
5 +});
added packages/shared/package.json +26 −0
@@ -0,0 +1,26 @@
1 +{
2 + "name": "@rareindex/shared",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + }
11 + },
12 + "scripts": {
13 + "build": "tsc -p tsconfig.json --noEmit",
14 + "typecheck": "tsc -p tsconfig.json --noEmit",
15 + "test": "vitest run --passWithNoTests"
16 + },
17 + "dependencies": {
18 + "pino": "^9.7.0",
19 + "zod": "^4.0.0"
20 + },
21 + "devDependencies": {
22 + "@types/node": "^24.0.0",
23 + "typescript": "^5.9.3",
24 + "vitest": "^3.2.0"
25 + }
26 +}
added packages/shared/src/dates.ts +64 −0
@@ -0,0 +1,64 @@
1 +const MONTHS: Record<string, number> = {
2 + jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, sept: 8, oct: 9, nov: 10, dec: 11,
3 +};
4 +
5 +/**
6 + * Parse common marketplace/auction date formats into a UTC Date (midnight when no time).
7 + * Returns null instead of guessing. Crawl time is never substituted for a sale date here.
8 + */
9 +export function parseSourceDate(raw: string | null | undefined, opts: { dayFirst?: boolean } = {}): Date | null {
10 + if (!raw) return null;
11 + const s = raw.trim().replace(/ /g, ' ');
12 + if (!s) return null;
13 +
14 + // ISO-ish
15 + if (/^\d{4}-\d{2}-\d{2}/.test(s)) {
16 + const d = new Date(s.length === 10 ? `${s}T00:00:00Z` : s);
17 + return Number.isNaN(d.getTime()) ? null : d;
18 + }
19 + // "Jan 5, 2024", "5 Jan 2024", "January 05 2024", "Sept 12, 2023 10:31 AM"
20 + const m1 = s.match(/\b([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s+(\d{4})/);
21 + if (m1) {
22 + const mo = MONTHS[m1[1]!.slice(0, 3).toLowerCase()];
23 + if (mo !== undefined) return new Date(Date.UTC(Number(m1[3]), mo, Number(m1[2])));
24 + }
25 + const m2 = s.match(/\b(\d{1,2})(?:st|nd|rd|th)?\s+([A-Za-z]{3,9})\.?,?\s+(\d{4})/);
26 + if (m2) {
27 + const mo = MONTHS[m2[2]!.slice(0, 3).toLowerCase()];
28 + if (mo !== undefined) return new Date(Date.UTC(Number(m2[3]), mo, Number(m2[1])));
29 + }
30 + // numeric 01/05/2024 or 05.01.2024 or 2024/01/05
31 + const m3 = s.match(/\b(\d{1,4})[./-](\d{1,2})[./-](\d{1,4})\b/);
32 + if (m3) {
33 + const a = Number(m3[1]);
34 + const b = Number(m3[2]);
35 + const c = Number(m3[3]);
36 + if (m3[1]!.length === 4) return new Date(Date.UTC(a, b - 1, c));
37 + let year = c;
38 + if (m3[3]!.length === 2) year = 2000 + c;
39 + const dayFirst = opts.dayFirst ?? a > 12;
40 + const [day, month] = dayFirst ? [a, b] : [b, a];
41 + if (month < 1 || month > 12 || day < 1 || day > 31) return null;
42 + return new Date(Date.UTC(year, month - 1, day));
43 + }
44 + // "Mar 2024" (month precision)
45 + const m4 = s.match(/\b([A-Za-z]{3,9})\.?\s+(\d{4})\b/);
46 + if (m4) {
47 + const mo = MONTHS[m4[1]!.slice(0, 3).toLowerCase()];
48 + if (mo !== undefined) return new Date(Date.UTC(Number(m4[2]), mo, 1));
49 + }
50 + const d = new Date(s);
51 + return Number.isNaN(d.getTime()) ? null : d;
52 +}
53 +
54 +export function toDateOnly(d: Date): string {
55 + return d.toISOString().slice(0, 10);
56 +}
57 +
58 +export function daysBetween(a: Date, b: Date): number {
59 + return Math.abs(a.getTime() - b.getTime()) / 86_400_000;
60 +}
61 +
62 +export function addDays(d: Date, days: number): Date {
63 + return new Date(d.getTime() + days * 86_400_000);
64 +}
added packages/shared/src/env.ts +37 −0
@@ -0,0 +1,37 @@
1 +import { z } from 'zod';
2 +
3 +/**
4 + * Server-side environment. Loaded lazily so browser bundles never import it.
5 + * Secrets are never logged (see logger redaction) and never exposed to the client.
6 + */
7 +const EnvSchema = z.object({
8 + DATABASE_URL: z.string().default('postgres://localhost:5432/rareindex'),
9 + FIRECRAWL_API_KEY: z.string().optional(),
10 + SCRAPFLY_API_KEY: z.string().optional(),
11 + ANTHROPIC_API_KEY: z.string().optional(),
12 + OPENAI_API_KEY: z.string().optional(),
13 + LOCAL_LLM_BASE_URL: z.string().optional(),
14 + LOCAL_LLM_API_KEY: z.string().optional(),
15 + POKEMONTCG_API_KEY: z.string().optional(),
16 + NEXT_PUBLIC_SITE_URL: z.string().default('http://localhost:3000'),
17 + SESSION_SECRET: z.string().default('dev-only-session-secret-change-me-please'),
18 + ADMIN_TOKEN: z.string().optional(),
19 + API_PORT: z.coerce.number().default(8211),
20 + WEB_PORT: z.coerce.number().default(8210),
21 + WORKER_CONCURRENCY: z.coerce.number().default(4),
22 + LOG_LEVEL: z.string().default('info'),
23 + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
24 + RI_DATA_DIR: z.string().default('./data'),
25 +});
26 +
27 +export type Env = z.infer<typeof EnvSchema>;
28 +
29 +let cached: Env | null = null;
30 +export function env(): Env {
31 + if (!cached) cached = EnvSchema.parse(process.env);
32 + return cached;
33 +}
34 +
35 +export function isProduction(): boolean {
36 + return env().NODE_ENV === 'production';
37 +}
added packages/shared/src/ids.ts +0 −0

Binary file not shown.

added packages/shared/src/index.ts +8 −0
@@ -0,0 +1,8 @@
1 +export * from './ids.js';
2 +export * from './money.js';
3 +export * from './text.js';
4 +export * from './dates.js';
5 +export * from './logger.js';
6 +export * from './env.js';
7 +export * from './schemas.js';
8 +export * from './stats.js';
added packages/shared/src/logger.ts +19 −0
@@ -0,0 +1,19 @@
1 +import pino from 'pino';
2 +
3 +const level = process.env.LOG_LEVEL ?? 'info';
4 +
5 +export const logger = pino({
6 + level,
7 + base: { service: process.env.RI_SERVICE ?? 'rareindex' },
8 + timestamp: pino.stdTimeFunctions.isoTime,
9 + redact: {
10 + paths: ['*.apiKey', '*.api_key', '*.authorization', 'req.headers.authorization', 'FIRECRAWL_API_KEY', 'SCRAPFLY_API_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY'],
11 + censor: '[redacted]',
12 + },
13 +});
14 +
15 +export type Logger = typeof logger;
16 +
17 +export function childLogger(bindings: Record<string, unknown>): Logger {
18 + return logger.child(bindings);
19 +}
added packages/shared/src/money.test.ts +65 −0
@@ -0,0 +1,65 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { parsePrice, formatMoney } from './money.js';
3 +import { parseSourceDate } from './dates.js';
4 +import { median, trimmedMean, ewMean, maxDrawdown } from './stats.js';
5 +import { normalizeForMatch, jaccard, slugify } from './text.js';
6 +
7 +describe('parsePrice', () => {
8 + it('parses US formats', () => {
9 + expect(parsePrice('$1,234.56')).toMatchObject({ amount: 1234.56, currency: 'USD' });
10 + expect(parsePrice('US $2,500.00')).toMatchObject({ amount: 2500, currency: 'USD' });
11 + expect(parsePrice('Sold for $420,000')).toMatchObject({ amount: 420000, currency: 'USD' });
12 + });
13 + it('parses EU formats', () => {
14 + expect(parsePrice('1.234,56 €')).toMatchObject({ amount: 1234.56, currency: 'EUR' });
15 + expect(parsePrice('EUR 12,50')).toMatchObject({ amount: 12.5, currency: 'EUR' });
16 + expect(parsePrice('2.500 €')).toMatchObject({ amount: 2500, currency: 'EUR' });
17 + });
18 + it('parses GBP/JPY/CAD', () => {
19 + expect(parsePrice('£12.99')).toMatchObject({ amount: 12.99, currency: 'GBP' });
20 + expect(parsePrice('¥15,000')).toMatchObject({ amount: 15000, currency: 'JPY' });
21 + expect(parsePrice('CA$1 200,00')).toMatchObject({ amount: 1200, currency: 'CAD' });
22 + expect(parsePrice('C$99')).toMatchObject({ amount: 99, currency: 'CAD' });
23 + });
24 + it('does not invent a currency', () => {
25 + expect(parsePrice('1500')).toMatchObject({ amount: 1500, currency: null });
26 + expect(parsePrice('1500', 'GBP')).toMatchObject({ amount: 1500, currency: 'GBP' });
27 + expect(parsePrice('no price')).toBeNull();
28 + });
29 + it('formats', () => {
30 + expect(formatMoney(8450)).toBe('$8,450');
31 + expect(formatMoney(12.5)).toBe('$12.50');
32 + expect(formatMoney(null)).toBe('—');
33 + });
34 +});
35 +
36 +describe('parseSourceDate', () => {
37 + it('handles common formats', () => {
38 + expect(parseSourceDate('2024-03-05')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');
39 + expect(parseSourceDate('Mar 5, 2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');
40 + expect(parseSourceDate('5 March 2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');
41 + expect(parseSourceDate('03/05/2024')?.toISOString()).toBe('2024-03-05T00:00:00.000Z');
42 + expect(parseSourceDate('05.03.2024', { dayFirst: true })?.toISOString()).toBe('2024-03-05T00:00:00.000Z');
43 + expect(parseSourceDate('garbage')).toBeNull();
44 + });
45 +});
46 +
47 +describe('stats', () => {
48 + it('median/trimmed/ew/drawdown', () => {
49 + expect(median([3, 1, 2])).toBe(2);
50 + expect(median([4, 1, 2, 3])).toBe(2.5);
51 + expect(trimmedMean([1, 2, 3, 4, 100], 0.2)).toBe(3);
52 + expect(ewMean([{ value: 100, ageDays: 0 }, { value: 50, ageDays: 1000 }])).toBeGreaterThan(90);
53 + expect(maxDrawdown([100, 120, 60, 90])).toBeCloseTo(0.5);
54 + });
55 +});
56 +
57 +describe('text', () => {
58 + it('normalises and compares', () => {
59 + const a = '1999 Pokemon Base Charizard 1st PSA 10';
60 + const b = 'Pokémon 1999 First Edition Charizard Holo PSA GEM MT 10';
61 + expect(normalizeForMatch(a)).toBe('1999 pokemon base charizard 1st psa 10');
62 + expect(jaccard(a, b)).toBeGreaterThan(0.3);
63 + expect(slugify('1999 Base Set Charizard #4/102 — 1st Edition')).toBe('1999-base-set-charizard-4-102-1st-edition');
64 + });
65 +});
added packages/shared/src/money.ts +123 −0
@@ -0,0 +1,123 @@
1 +/**
2 + * Currency handling. Prices are stored as integer minor units? No — collectibles span
3 + * cents to eight figures and many sources report fractional amounts; we use NUMERIC(18,4)
4 + * in the database and JS numbers at the edges. Never mix currencies without an FX row.
5 + */
6 +export const SUPPORTED_CURRENCIES = [
7 + 'USD', 'CAD', 'EUR', 'GBP', 'JPY', 'CHF', 'AUD', 'HKD', 'SGD', 'CNY', 'KRW', 'SEK', 'NOK', 'DKK', 'NZD', 'MXN', 'BRL', 'INR', 'PLN', 'CZK', 'TWD', 'THB', 'AED',
8 +] as const;
9 +export type CurrencyCode = (typeof SUPPORTED_CURRENCIES)[number];
10 +
11 +export const DISPLAY_CURRENCIES: CurrencyCode[] = ['USD', 'CAD', 'EUR', 'GBP', 'JPY'];
12 +
13 +const SYMBOL_MAP: Array<[RegExp, CurrencyCode]> = [
14 + [/\bUS\s?\$|\bUSD\b|U\.S\.\s?\$/i, 'USD'],
15 + [/\bCA\s?\$|\bC\$|\bCAD\b|CDN\s?\$/i, 'CAD'],
16 + [/\bAU\s?\$|\bA\$|\bAUD\b/i, 'AUD'],
17 + [/\bNZ\s?\$|\bNZD\b/i, 'NZD'],
18 + [/\bHK\s?\$|\bHKD\b/i, 'HKD'],
19 + [/\bS\$|\bSGD\b/i, 'SGD'],
20 + [/\bMX\s?\$|\bMXN\b/i, 'MXN'],
21 + [/€|\bEUR\b/i, 'EUR'],
22 + [/£|\bGBP\b/i, 'GBP'],
23 + [/¥|¥|\bJPY\b/i, 'JPY'],
24 + [/\bCHF\b|\bFr\.\s?\d/i, 'CHF'],
25 + [/\bCNY\b|\bRMB\b/i, 'CNY'],
26 + [/₩|\bKRW\b/i, 'KRW'],
27 + [/\bSEK\b|\bkr\b/i, 'SEK'],
28 + [/\bNOK\b/i, 'NOK'],
29 + [/\bDKK\b/i, 'DKK'],
30 + [/\bAED\b/i, 'AED'],
31 + [/\bINR\b|₹/i, 'INR'],
32 + [/\bBRL\b|R\$/i, 'BRL'],
33 + [/\bPLN\b|zł/i, 'PLN'],
34 + [/\bCZK\b|Kč/i, 'CZK'],
35 + [/\bTWD\b|NT\$/i, 'TWD'],
36 + [/\bTHB\b|฿/i, 'THB'],
37 +];
38 +
39 +export interface ParsedPrice {
40 + amount: number;
41 + currency: CurrencyCode | null;
42 + /** 0–1 confidence that the parse is correct */
43 + confidence: number;
44 +}
45 +
46 +/**
47 + * Parse a human-formatted price. Handles "$1,234.56", "1.234,56 €", "£12", "¥15,000", "US $2,500.00",
48 + * "CA$1 200,00", "1 234,56 EUR", "2.500" (ambiguous → treated as thousands separator when 3 trailing digits).
49 + * Returns null when no number is found. Never guesses a currency when none is present
50 + * (caller supplies `defaultCurrency` if the source is single-currency).
51 + */
52 +export function parsePrice(raw: string | null | undefined, defaultCurrency?: CurrencyCode): ParsedPrice | null {
53 + if (!raw) return null;
54 + const text = raw.replace(/ /g, ' ').trim();
55 + if (!text) return null;
56 +
57 + let currency: CurrencyCode | null = null;
58 + for (const [re, code] of SYMBOL_MAP) {
59 + if (re.test(text)) {
60 + currency = code;
61 + break;
62 + }
63 + }
64 + // Bare "$" defaults to USD only when caller allows via defaultCurrency or nothing else matched.
65 + if (!currency && /\$/.test(text)) currency = defaultCurrency ?? 'USD';
66 + if (!currency) currency = defaultCurrency ?? null;
67 +
68 + const m = text.match(/-?\d[\d\s.,']*\d|\d/);
69 + if (!m) return null;
70 + let num = m[0].replace(/[\s']/g, '');
71 + const negative = num.startsWith('-');
72 + num = num.replace('-', '');
73 +
74 + const lastComma = num.lastIndexOf(',');
75 + const lastDot = num.lastIndexOf('.');
76 + let confidence = 0.95;
77 + if (lastComma >= 0 && lastDot >= 0) {
78 + // Whichever separator is last is the decimal separator.
79 + if (lastComma > lastDot) num = num.replace(/\./g, '').replace(',', '.');
80 + else num = num.replace(/,/g, '');
81 + } else if (lastComma >= 0) {
82 + const tail = num.length - lastComma - 1;
83 + if (tail === 3 && (num.match(/,/g)?.length ?? 0) >= 1 && !/^\d{1,3},\d{3}$/.test(num)) {
84 + num = num.replace(/,/g, '');
85 + } else if (tail === 3) {
86 + // "2,500" → ambiguous; thousands in EN sources, decimal in some EU sources. Prefer thousands.
87 + num = num.replace(/,/g, '');
88 + confidence = 0.8;
89 + } else {
90 + num = num.replace(',', '.');
91 + }
92 + } else if (lastDot >= 0) {
93 + const tail = num.length - lastDot - 1;
94 + const dots = num.match(/\./g)?.length ?? 0;
95 + if (dots > 1 || (tail === 3 && currency === 'EUR')) {
96 + num = num.replace(/\./g, '');
97 + confidence = dots > 1 ? 0.9 : 0.7;
98 + }
99 + }
100 + const amount = Number.parseFloat(num);
101 + if (!Number.isFinite(amount)) return null;
102 + if (currency === 'JPY' || currency === 'KRW') confidence = Math.min(confidence, 0.9);
103 + return { amount: negative ? -amount : amount, currency, confidence };
104 +}
105 +
106 +export function formatMoney(amount: number | null | undefined, currency: CurrencyCode = 'USD', opts: { compact?: boolean; maximumFractionDigits?: number } = {}): string {
107 + if (amount === null || amount === undefined || !Number.isFinite(amount)) return '—';
108 + const zeroDecimal = currency === 'JPY' || currency === 'KRW';
109 + const fmt = new Intl.NumberFormat('en-US', {
110 + style: 'currency',
111 + currency,
112 + notation: opts.compact ? 'compact' : 'standard',
113 + maximumFractionDigits: opts.maximumFractionDigits ?? (zeroDecimal ? 0 : amount >= 1000 ? 0 : 2),
114 + minimumFractionDigits: zeroDecimal || amount >= 1000 || opts.compact ? 0 : 2,
115 + });
116 + return fmt.format(amount);
117 +}
118 +
119 +export function formatPct(value: number | null | undefined, digits = 2): string {
120 + if (value === null || value === undefined || !Number.isFinite(value)) return '—';
121 + const sign = value > 0 ? '+' : '';
122 + return `${sign}${(value * 100).toFixed(digits)}%`;
123 +}
added packages/shared/src/schemas.ts +248 −0
@@ -0,0 +1,248 @@
1 +import { z } from 'zod';
2 +import { SUPPORTED_CURRENCIES } from './money.js';
3 +
4 +/**
5 + * Canonical Zod schemas. These are the contracts between connectors, the ingestion pipeline,
6 + * the database layer, and the apps. Source-specific quirks must be resolved BEFORE data
7 + * reaches these shapes (see CLAUDE.md §108, §196.6).
8 + */
9 +
10 +export const CurrencySchema = z.enum(SUPPORTED_CURRENCIES);
11 +
12 +export const SourceTypeSchema = z.enum([
13 + 'marketplace', 'auction_house', 'dealer', 'pricing_guide', 'grading_company', 'manufacturer', 'museum', 'archive',
14 + 'catalog', 'collector_database', 'community', 'forum', 'news', 'analytics_provider', 'registry', 'official_source',
15 +]);
16 +export type SourceType = z.infer<typeof SourceTypeSchema>;
17 +
18 +export const EngineSchema = z.enum(['api', 'feed', 'firecrawl', 'scrapfly', 'browser', 'manual']);
19 +export type Engine = z.infer<typeof EngineSchema>;
20 +
21 +export const RecordKindSchema = z.enum(['sale', 'listing', 'auction_lot', 'price_observation', 'catalog_item', 'population_report', 'news_item']);
22 +export type RecordKind = z.infer<typeof RecordKindSchema>;
23 +
24 +/** Immutable raw payload captured from a source. Never mutated after insert (§108). */
25 +export const RawRecordSchema = z.object({
26 + connectorId: z.string(),
27 + sourceId: z.string(),
28 + engine: EngineSchema,
29 + url: z.string().url().or(z.string().min(1)),
30 + externalId: z.string().nullable().default(null),
31 + kind: RecordKindSchema,
32 + fetchedAt: z.coerce.date(),
33 + contentHash: z.string(),
34 + httpStatus: z.number().int().nullable().default(null),
35 + payload: z.unknown(),
36 + /** raw HTML/markdown snapshot key in object storage or inline text (bounded) */
37 + snapshotRef: z.string().nullable().default(null),
38 + parserVersion: z.string(),
39 + connectorVersion: z.string(),
40 +});
41 +export type RawRecord = z.infer<typeof RawRecordSchema>;
42 +
43 +/** Attributes used for canonical asset identity; anything unknown must be null, never guessed (§192). */
44 +export const AssetAttributesSchema = z.object({
45 + categorySlug: z.string(),
46 + subcategorySlug: z.string().nullable().default(null),
47 + franchise: z.string().nullable().default(null),
48 + brand: z.string().nullable().default(null),
49 + series: z.string().nullable().default(null),
50 + set: z.string().nullable().default(null),
51 + setCode: z.string().nullable().default(null),
52 + name: z.string(),
53 + model: z.string().nullable().default(null),
54 + reference: z.string().nullable().default(null),
55 + number: z.string().nullable().default(null),
56 + year: z.number().int().nullable().default(null),
57 + edition: z.string().nullable().default(null),
58 + variant: z.string().nullable().default(null),
59 + language: z.string().nullable().default(null),
60 + region: z.string().nullable().default(null),
61 + country: z.string().nullable().default(null),
62 + material: z.string().nullable().default(null),
63 + size: z.string().nullable().default(null),
64 + color: z.string().nullable().default(null),
65 + rarity: z.string().nullable().default(null),
66 + productionQuantity: z.number().int().nullable().default(null),
67 + originalMsrp: z.number().nullable().default(null),
68 + originalMsrpCurrency: CurrencySchema.nullable().default(null),
69 + /** Deterministic identifiers: upc, isbn, sku, scryfall_id, tcgplayer_id, bricklink_id, style_code, ref, cert… */
70 + identifiers: z.record(z.string(), z.string()).default({}),
71 + metadata: z.record(z.string(), z.unknown()).default({}),
72 +});
73 +export type AssetAttributes = z.infer<typeof AssetAttributesSchema>;
74 +
75 +export const GradeSchema = z.object({
76 + grader: z.string().nullable().default(null), // PSA, BGS, CGC, SGC, TAG, WATA, VGA, PCGS, NGC, CBCS, raw
77 + grade: z.string().nullable().default(null), // "10", "9.5", "9.8", "MS65"
78 + qualifier: z.string().nullable().default(null), // "OC", "Signature Series", "Black Label"
79 + certificationNumber: z.string().nullable().default(null),
80 +});
81 +export type Grade = z.infer<typeof GradeSchema>;
82 +
83 +export const ConditionSchema = z.object({
84 + /** category-specific normalised condition slug (see taxonomy condition scales) */
85 + condition: z.string().nullable().default(null),
86 + conditionRaw: z.string().nullable().default(null),
87 + completeness: z.string().nullable().default(null), // sealed, cib, loose, box_papers, papers_only…
88 +});
89 +
90 +const BaseNormalized = z.object({
91 + connectorId: z.string(),
92 + sourceId: z.string(),
93 + sourceUrl: z.string(),
94 + externalId: z.string().nullable().default(null),
95 + rawTitle: z.string(),
96 + description: z.string().nullable().default(null),
97 + imageUrls: z.array(z.string()).default([]),
98 + attributes: AssetAttributesSchema,
99 + grade: GradeSchema.prefault({}),
100 + condition: ConditionSchema.prefault({}),
101 + observedAt: z.coerce.date(),
102 + /** 0–1 confidence in the extraction & identification */
103 + confidence: z.number().min(0).max(1).default(0.8),
104 + parserVersion: z.string(),
105 +});
106 +
107 +export const NormalizedSaleSchema = BaseNormalized.extend({
108 + kind: z.literal('sale'),
109 + saleType: z.enum(['auction', 'fixed_price', 'best_offer', 'private', 'dealer', 'unknown']).default('unknown'),
110 + saleDate: z.coerce.date(),
111 + price: z.number().nonnegative(),
112 + currency: CurrencySchema,
113 + buyerPremiumIncluded: z.boolean().nullable().default(null),
114 + quantity: z.number().int().positive().default(1),
115 + isBundle: z.boolean().default(false),
116 + location: z.string().nullable().default(null),
117 + auctionHouse: z.string().nullable().default(null),
118 + lotNumber: z.string().nullable().default(null),
119 +});
120 +export type NormalizedSale = z.infer<typeof NormalizedSaleSchema>;
121 +
122 +export const NormalizedListingSchema = BaseNormalized.extend({
123 + kind: z.literal('listing'),
124 + listingType: z.enum(['fixed_price', 'auction', 'best_offer', 'ask', 'unknown']).default('unknown'),
125 + price: z.number().nonnegative().nullable(),
126 + currency: CurrencySchema.nullable(),
127 + seller: z.string().nullable().default(null),
128 + sellerReputation: z.string().nullable().default(null),
129 + location: z.string().nullable().default(null),
130 + shippingCost: z.number().nullable().default(null),
131 + quantity: z.number().int().nullable().default(null),
132 + listedAt: z.coerce.date().nullable().default(null),
133 + endsAt: z.coerce.date().nullable().default(null),
134 + availability: z.enum(['available', 'sold', 'ended', 'removed', 'unknown']).default('available'),
135 + bidCount: z.number().int().nullable().default(null),
136 +});
137 +export type NormalizedListing = z.infer<typeof NormalizedListingSchema>;
138 +
139 +export const NormalizedAuctionLotSchema = BaseNormalized.extend({
140 + kind: z.literal('auction_lot'),
141 + auctionHouse: z.string(),
142 + auctionName: z.string().nullable().default(null),
143 + lotNumber: z.string().nullable().default(null),
144 + startsAt: z.coerce.date().nullable().default(null),
145 + endsAt: z.coerce.date().nullable().default(null),
146 + estimateLow: z.number().nullable().default(null),
147 + estimateHigh: z.number().nullable().default(null),
148 + currentBid: z.number().nullable().default(null),
149 + currency: CurrencySchema.nullable(),
150 + status: z.enum(['upcoming', 'live', 'ended', 'unknown']).default('unknown'),
151 + location: z.string().nullable().default(null),
152 +});
153 +export type NormalizedAuctionLot = z.infer<typeof NormalizedAuctionLotSchema>;
154 +
155 +/** Price-guide style observation (e.g. Scryfall market price). Not a transaction (§111 spirit). */
156 +export const NormalizedPriceObservationSchema = BaseNormalized.extend({
157 + kind: z.literal('price_observation'),
158 + priceKind: z.enum(['market', 'low', 'mid', 'high', 'trend', 'average_7d', 'average_30d', 'last_sale_reported', 'guide_value']),
159 + price: z.number().nonnegative(),
160 + currency: CurrencySchema,
161 + observationDate: z.coerce.date(),
162 + sampleSize: z.number().int().nullable().default(null),
163 +});
164 +export type NormalizedPriceObservation = z.infer<typeof NormalizedPriceObservationSchema>;
165 +
166 +/** Catalog entry from a manufacturer/reference database: creates or enriches canonical assets. */
167 +export const NormalizedCatalogItemSchema = BaseNormalized.extend({
168 + kind: z.literal('catalog_item'),
169 + releaseDate: z.coerce.date().nullable().default(null),
170 +});
171 +export type NormalizedCatalogItem = z.infer<typeof NormalizedCatalogItemSchema>;
172 +
173 +export const NormalizedPopulationReportSchema = z.object({
174 + kind: z.literal('population_report'),
175 + connectorId: z.string(),
176 + sourceId: z.string(),
177 + sourceUrl: z.string(),
178 + grader: z.string(),
179 + attributes: AssetAttributesSchema,
180 + reportDate: z.coerce.date(),
181 + total: z.number().int().nonnegative(),
182 + byGrade: z.record(z.string(), z.number().int().nonnegative()),
183 + parserVersion: z.string(),
184 + confidence: z.number().min(0).max(1).default(0.9),
185 +});
186 +export type NormalizedPopulationReport = z.infer<typeof NormalizedPopulationReportSchema>;
187 +
188 +export const NormalizedNewsItemSchema = z.object({
189 + kind: z.literal('news_item'),
190 + connectorId: z.string(),
191 + sourceId: z.string(),
192 + sourceUrl: z.string(),
193 + title: z.string(),
194 + summary: z.string().nullable().default(null),
195 + publishedAt: z.coerce.date().nullable().default(null),
196 + categorySlugs: z.array(z.string()).default([]),
197 + imageUrl: z.string().nullable().default(null),
198 + parserVersion: z.string(),
199 +});
200 +export type NormalizedNewsItem = z.infer<typeof NormalizedNewsItemSchema>;
201 +
202 +export const NormalizedRecordSchema = z.discriminatedUnion('kind', [
203 + NormalizedSaleSchema,
204 + NormalizedListingSchema,
205 + NormalizedAuctionLotSchema,
206 + NormalizedPriceObservationSchema,
207 + NormalizedCatalogItemSchema,
208 + NormalizedPopulationReportSchema,
209 + NormalizedNewsItemSchema,
210 +]);
211 +export type NormalizedRecord = z.infer<typeof NormalizedRecordSchema>;
212 +
213 +export const ConnectorStatusSchema = z.enum(['healthy', 'degraded', 'failing', 'paused', 'unknown']);
214 +export const ConnectorHealthSchema = z.object({
215 + connector: z.string(),
216 + status: ConnectorStatusSchema,
217 + success_rate_24h: z.number().min(0).max(1).nullable(),
218 + pages_attempted: z.number().int().nonnegative(),
219 + pages_success: z.number().int().nonnegative(),
220 + firecrawl_success_rate: z.number().min(0).max(1).nullable(),
221 + scrapfly_fallback_rate: z.number().min(0).max(1).nullable(),
222 + parse_failure_rate: z.number().min(0).max(1).nullable(),
223 + last_success: z.string().nullable(),
224 + last_error: z.string().nullable(),
225 + schema_version: z.string(),
226 + records_24h: z.number().int().nonnegative(),
227 + duplicates_24h: z.number().int().nonnegative(),
228 + anomalies: z.array(z.string()).default([]),
229 +});
230 +export type ConnectorHealth = z.infer<typeof ConnectorHealthSchema>;
231 +
232 +export const ExtractionResultSchema = z.object({
233 + success: z.boolean(),
234 + engine: EngineSchema,
235 + url: z.string(),
236 + finalUrl: z.string().nullable().default(null),
237 + httpStatus: z.number().int().nullable().default(null),
238 + html: z.string().nullable().default(null),
239 + markdown: z.string().nullable().default(null),
240 + json: z.unknown().nullable().default(null),
241 + qualityScore: z.number().min(0).max(1).default(0),
242 + requiresReview: z.boolean().default(false),
243 + error: z.string().nullable().default(null),
244 + costCredits: z.number().nullable().default(null),
245 + durationMs: z.number().nullable().default(null),
246 + fetchedAt: z.coerce.date(),
247 +});
248 +export type ExtractionResult = z.infer<typeof ExtractionResultSchema>;
added packages/shared/src/stats.ts +109 −0
@@ -0,0 +1,109 @@
1 +/** Small, dependency-free numeric helpers used by valuation, indices and scores. */
2 +
3 +export function median(values: number[]): number | null {
4 + if (values.length === 0) return null;
5 + const s = [...values].sort((a, b) => a - b);
6 + const mid = Math.floor(s.length / 2);
7 + return s.length % 2 ? s[mid]! : (s[mid - 1]! + s[mid]!) / 2;
8 +}
9 +
10 +export function mean(values: number[]): number | null {
11 + if (values.length === 0) return null;
12 + return values.reduce((a, b) => a + b, 0) / values.length;
13 +}
14 +
15 +export function quantile(values: number[], q: number): number | null {
16 + if (values.length === 0) return null;
17 + const s = [...values].sort((a, b) => a - b);
18 + const pos = (s.length - 1) * q;
19 + const lo = Math.floor(pos);
20 + const hi = Math.ceil(pos);
21 + if (lo === hi) return s[lo]!;
22 + return s[lo]! + (s[hi]! - s[lo]!) * (pos - lo);
23 +}
24 +
25 +/** Trimmed mean dropping `trim` fraction from each tail (e.g. 0.1). */
26 +export function trimmedMean(values: number[], trim = 0.1): number | null {
27 + if (values.length === 0) return null;
28 + const s = [...values].sort((a, b) => a - b);
29 + const k = Math.floor(s.length * trim);
30 + const core = s.slice(k, s.length - k);
31 + return mean(core.length ? core : s);
32 +}
33 +
34 +export function stddev(values: number[]): number | null {
35 + if (values.length < 2) return null;
36 + const m = mean(values)!;
37 + const v = values.reduce((acc, x) => acc + (x - m) ** 2, 0) / (values.length - 1);
38 + return Math.sqrt(v);
39 +}
40 +
41 +/** Median absolute deviation (robust dispersion). */
42 +export function mad(values: number[]): number | null {
43 + const m = median(values);
44 + if (m === null) return null;
45 + return median(values.map((v) => Math.abs(v - m)));
46 +}
47 +
48 +/** Exponentially weighted mean; `halfLifeDays` controls decay by age (days). Most recent = highest weight. */
49 +export function ewMean(points: Array<{ value: number; ageDays: number }>, halfLifeDays = 60): number | null {
50 + if (points.length === 0) return null;
51 + let num = 0;
52 + let den = 0;
53 + for (const p of points) {
54 + const w = Math.pow(0.5, Math.max(0, p.ageDays) / halfLifeDays);
55 + num += w * p.value;
56 + den += w;
57 + }
58 + return den > 0 ? num / den : null;
59 +}
60 +
61 +export function weightedMean(points: Array<{ value: number; weight: number }>): number | null {
62 + let num = 0;
63 + let den = 0;
64 + for (const p of points) {
65 + num += p.value * p.weight;
66 + den += p.weight;
67 + }
68 + return den > 0 ? num / den : null;
69 +}
70 +
71 +export function clamp(x: number, lo: number, hi: number): number {
72 + return Math.min(hi, Math.max(lo, x));
73 +}
74 +
75 +/** Percent change guarded against zero/null. */
76 +export function pctChange(from: number | null | undefined, to: number | null | undefined): number | null {
77 + if (from === null || from === undefined || to === null || to === undefined || from === 0) return null;
78 + return (to - from) / from;
79 +}
80 +
81 +/** Max drawdown of a value series (0..1). */
82 +export function maxDrawdown(series: number[]): number | null {
83 + if (series.length < 2) return null;
84 + let peak = series[0]!;
85 + let mdd = 0;
86 + for (const v of series) {
87 + if (v > peak) peak = v;
88 + if (peak > 0) mdd = Math.max(mdd, (peak - v) / peak);
89 + }
90 + return mdd;
91 +}
92 +
93 +/** Annualised volatility of log returns from an evenly spaced series. */
94 +export function volatility(series: number[], periodsPerYear = 365): number | null {
95 + if (series.length < 3) return null;
96 + const rets: number[] = [];
97 + for (let i = 1; i < series.length; i++) {
98 + const a = series[i - 1]!;
99 + const b = series[i]!;
100 + if (a > 0 && b > 0) rets.push(Math.log(b / a));
101 + }
102 + const sd = stddev(rets);
103 + return sd === null ? null : sd * Math.sqrt(periodsPerYear);
104 +}
105 +
106 +export function round(x: number, digits = 2): number {
107 + const f = 10 ** digits;
108 + return Math.round(x * f) / f;
109 +}
added packages/shared/src/text.ts +61 −0
@@ -0,0 +1,61 @@
1 +/** Text normalisation helpers shared by entity resolution, search and connectors. */
2 +
3 +export function slugify(input: string, maxLength = 96): string {
4 + const s = input
5 + .normalize('NFKD')
6 + .replace(/[̀-ͯ]/g, '')
7 + .toLowerCase()
8 + .replace(/&/g, ' and ')
9 + .replace(/[^a-z0-9]+/g, '-')
10 + .replace(/^-+|-+$/g, '');
11 + return s.length > maxLength ? s.slice(0, maxLength).replace(/-+$/g, '') : s;
12 +}
13 +
14 +/** Aggressive normalisation for matching: lowercase, ASCII, punctuation removed, common noise words dropped. */
15 +const NOISE = new Set(['the', 'a', 'an', 'of', 'and', '&', 'card', 'cards', 'lot', 'rare', 'mint', 'nm', 'lp', 'mp', 'hp', 'wow', 'look', 'nice', 'beautiful', 'gorgeous', 'pop', 'gem', 'mt', 'l@@k']);
16 +export function normalizeForMatch(input: string): string {
17 + return input
18 + .normalize('NFKD')
19 + .replace(/[̀-ͯ]/g, '')
20 + .toLowerCase()
21 + .replace(/[#№]/g, ' ')
22 + .replace(/[^a-z0-9./\- ]+/g, ' ')
23 + .split(/\s+/)
24 + .filter((t) => t && !NOISE.has(t))
25 + .join(' ')
26 + .trim();
27 +}
28 +
29 +export function tokens(input: string): string[] {
30 + return normalizeForMatch(input).split(' ').filter(Boolean);
31 +}
32 +
33 +/** Jaccard similarity of token sets (0–1). */
34 +export function jaccard(a: string, b: string): number {
35 + const A = new Set(tokens(a));
36 + const B = new Set(tokens(b));
37 + if (A.size === 0 && B.size === 0) return 1;
38 + let inter = 0;
39 + for (const t of A) if (B.has(t)) inter++;
40 + return inter / (A.size + B.size - inter);
41 +}
42 +
43 +export function truncate(s: string, n: number): string {
44 + return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
45 +}
46 +
47 +export function compactWhitespace(s: string): string {
48 + return s.replace(/\s+/g, ' ').trim();
49 +}
50 +
51 +/** Extract a 4-digit year plausible for collectibles (1800–current+1). */
52 +export function extractYear(s: string): number | null {
53 + const now = new Date().getUTCFullYear() + 1;
54 + const m = s.match(/\b(1[89]\d{2}|20\d{2})\b/g);
55 + if (!m) return null;
56 + for (const y of m) {
57 + const n = Number(y);
58 + if (n >= 1800 && n <= now) return n;
59 + }
60 + return null;
61 +}
added packages/shared/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": "src",
5 + "outDir": "dist",
6 + "noEmit": true
7 + },
8 + "include": ["src"]
9 +}
added packages/shared/vitest.config.ts +5 −0
@@ -0,0 +1,5 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: { include: ['src/**/*.test.ts'] },
5 +});
added packages/taxonomy/package.json +26 −0
@@ -0,0 +1,26 @@
1 +{
2 + "name": "@rareindex/taxonomy",
3 + "version": "0.1.0",
4 + "private": true,
5 + "type": "module",
6 + "exports": {
7 + ".": {
8 + "types": "./src/index.ts",
9 + "default": "./src/index.ts"
10 + }
11 + },
12 + "scripts": {
13 + "build": "tsc -p tsconfig.json --noEmit",
14 + "typecheck": "tsc -p tsconfig.json --noEmit",
15 + "test": "vitest run --passWithNoTests"
16 + },
17 + "dependencies": {
18 + "@rareindex/shared": "workspace:*",
19 + "zod": "^4.0.0"
20 + },
21 + "devDependencies": {
22 + "@types/node": "^24.0.0",
23 + "typescript": "^5.9.3",
24 + "vitest": "^3.2.0"
25 + }
26 +}
added packages/taxonomy/src/index.ts +230 −0
@@ -0,0 +1,230 @@
1 +import { z } from 'zod';
2 +import categoriesJson from '../../../data/taxonomy/categories.json' with { type: 'json' };
3 +import conditionsJson from '../../../data/taxonomy/conditions.json' with { type: 'json' };
4 +import gradersJson from '../../../data/taxonomy/graders.json' with { type: 'json' };
5 +import indicesJson from '../../../data/taxonomy/indices.json' with { type: 'json' };
6 +
7 +/**
8 + * Taxonomy loader. The JSON files under data/taxonomy are the seed; the database `categories`
9 + * table is the runtime source of truth (it may contain approved proposals). This module offers
10 + * pure helpers usable from connectors, workers and the web layer without a DB round-trip.
11 + */
12 +
13 +const NodeSchema = z.object({
14 + slug: z.string(),
15 + parent: z.string().optional(),
16 + name: z.string(),
17 + short: z.string().optional(),
18 + phase: z.number().int().min(1).max(3),
19 + index: z.string().nullable().optional(),
20 + conditionScale: z.string().optional(),
21 + graders: z.array(z.string()).optional(),
22 + compliance: z.array(z.string()).optional(),
23 + description: z.string().optional(),
24 + icon: z.string().optional(),
25 +});
26 +export type TaxonomyNode = z.infer<typeof NodeSchema>;
27 +
28 +export interface CategoryNode extends TaxonomyNode {
29 + level: number;
30 + familySlug: string;
31 + children: string[];
32 + sortOrder: number;
33 +}
34 +
35 +const parsedNodes = z.array(NodeSchema).parse(categoriesJson.nodes);
36 +
37 +const bySlug = new Map<string, CategoryNode>();
38 +for (const [i, n] of parsedNodes.entries()) {
39 + bySlug.set(n.slug, { ...n, level: 0, familySlug: n.slug, children: [], sortOrder: i });
40 +}
41 +for (const n of bySlug.values()) {
42 + if (n.parent) {
43 + const p = bySlug.get(n.parent);
44 + if (!p) throw new Error(`taxonomy: parent ${n.parent} of ${n.slug} not found`);
45 + p.children.push(n.slug);
46 + }
47 +}
48 +function resolve(n: CategoryNode): void {
49 + if (!n.parent) return;
50 + const p = bySlug.get(n.parent)!;
51 + resolve(p);
52 + n.level = p.level + 1;
53 + n.familySlug = p.familySlug;
54 + n.index = n.index ?? p.index ?? null;
55 + n.conditionScale = n.conditionScale ?? p.conditionScale;
56 + n.graders = n.graders ?? p.graders;
57 +}
58 +for (const n of bySlug.values()) resolve(n);
59 +
60 +export const CATEGORIES: readonly CategoryNode[] = [...bySlug.values()];
61 +export const FAMILIES: readonly CategoryNode[] = CATEGORIES.filter((c) => c.level === 0);
62 +
63 +export function getCategory(slug: string): CategoryNode | undefined {
64 + return bySlug.get(slug);
65 +}
66 +export function requireCategory(slug: string): CategoryNode {
67 + const c = bySlug.get(slug);
68 + if (!c) throw new Error(`Unknown category slug: ${slug}`);
69 + return c;
70 +}
71 +export function categoryFamily(slug: string): string {
72 + return bySlug.get(slug)?.familySlug ?? slug;
73 +}
74 +export function categoryPath(slug: string): CategoryNode[] {
75 + const out: CategoryNode[] = [];
76 + let cur = bySlug.get(slug);
77 + while (cur) {
78 + out.unshift(cur);
79 + cur = cur.parent ? bySlug.get(cur.parent) : undefined;
80 + }
81 + return out;
82 +}
83 +export function descendants(slug: string): string[] {
84 + const out: string[] = [];
85 + const stack = [slug];
86 + while (stack.length) {
87 + const s = stack.pop()!;
88 + const n = bySlug.get(s);
89 + if (!n) continue;
90 + for (const c of n.children) {
91 + out.push(c);
92 + stack.push(c);
93 + }
94 + }
95 + return out;
96 +}
97 +export function phaseCategories(phase: 1 | 2 | 3): CategoryNode[] {
98 + return CATEGORIES.filter((c) => c.phase <= phase);
99 +}
100 +
101 +// ---------- Condition scales ----------
102 +export interface ConditionScale {
103 + values: string[];
104 + completeness: string[];
105 + aliases: Record<string, string | null>;
106 +}
107 +export const CONDITION_SCALES: Record<string, ConditionScale> = conditionsJson.scales as Record<string, ConditionScale>;
108 +
109 +export function conditionScaleFor(categorySlug: string): ConditionScale {
110 + const scale = bySlug.get(categorySlug)?.conditionScale ?? 'general';
111 + return CONDITION_SCALES[scale] ?? CONDITION_SCALES.general!;
112 +}
113 +
114 +/** Map a raw condition string to the category's normalised slug; null when unknown (never guess). */
115 +export function normalizeCondition(categorySlug: string, raw: string | null | undefined): string | null {
116 + if (!raw) return null;
117 + const scale = conditionScaleFor(categorySlug);
118 + const key = raw.trim().toLowerCase().replace(/\s+/g, ' ');
119 + if (key in scale.aliases) return scale.aliases[key] ?? null;
120 + const asSlug = key.replace(/[\s-]+/g, '_');
121 + if (scale.values.includes(asSlug)) return asSlug;
122 + // Try contained alias (e.g. "Condition: Near Mint or Better")
123 + for (const [alias, slug] of Object.entries(scale.aliases)) {
124 + if (alias.length >= 4 && key.includes(alias)) return slug;
125 + }
126 + return null;
127 +}
128 +
129 +export function conditionRank(categorySlug: string, condition: string | null): number | null {
130 + if (!condition) return null;
131 + const i = conditionScaleFor(categorySlug).values.indexOf(condition);
132 + return i >= 0 ? i : null;
133 +}
134 +
135 +// ---------- Graders ----------
136 +export interface GraderDef {
137 + slug: string;
138 + name: string;
139 + categories: string[];
140 + scale: { type: 'numeric' | 'sheldon' | 'descriptive'; values: string[]; top: string };
141 + populationUrl?: string;
142 + verifyUrl?: string;
143 + aliases: string[];
144 +}
145 +export const GRADERS: readonly GraderDef[] = gradersJson.graders as GraderDef[];
146 +const graderByAlias = new Map<string, GraderDef>();
147 +for (const g of GRADERS) {
148 + graderByAlias.set(g.slug, g);
149 + for (const a of g.aliases) graderByAlias.set(a.toLowerCase(), g);
150 +}
151 +export function getGrader(slugOrAlias: string | null | undefined): GraderDef | undefined {
152 + if (!slugOrAlias) return undefined;
153 + return graderByAlias.get(slugOrAlias.trim().toLowerCase());
154 +}
155 +
156 +const GRADE_RE = /\b(PSA|BGS|BECKETT|CGC|SGC|TAG|ACE|CBCS|PGX|WATA|VGA|PCGS|NGC|ANACS|ICCS|PMG)\b[\s:-]*(?:GEM\s*MT|GEM\s*MINT|MINT|NM-?MT|PRISTINE|BLACK\s*LABEL)?[\s:-]*(\d{1,3}(?:\.\d)?)?/i;
157 +
158 +/** Extract grader + grade from a free-text title. Returns nulls when absent. */
159 +export function parseGradeFromTitle(title: string): { grader: string | null; grade: string | null; qualifier: string | null } {
160 + const m = title.match(GRADE_RE);
161 + if (!m) {
162 + if (/\b(raw|ungraded)\b/i.test(title)) return { grader: 'raw', grade: null, qualifier: null };
163 + return { grader: null, grade: null, qualifier: null };
164 + }
165 + const grader = getGrader(m[1]!)?.slug ?? m[1]!.toLowerCase();
166 + let grade = m[2] ?? null;
167 + const g = getGrader(grader);
168 + if (grade && g && g.scale.values.length && !g.scale.values.includes(grade)) {
169 + // e.g. "PSA 10" where 10 is fine; "PSA 2021" is a year → reject
170 + if (Number(grade) > Number(g.scale.top)) grade = null;
171 + }
172 + let qualifier: string | null = null;
173 + if (/black\s*label/i.test(title)) qualifier = 'Black Label';
174 + else if (/pristine/i.test(title) && grader === 'bgs') qualifier = 'Pristine';
175 + else if (/signature\s*series/i.test(title)) qualifier = 'Signature Series';
176 + else if (/\b(OC|MC|ST|PD|MK)\b/.test(title) && grader === 'psa') qualifier = title.match(/\b(OC|MC|ST|PD|MK)\b/)![1]!;
177 + return { grader, grade, qualifier };
178 +}
179 +
180 +/** Variant key used by asset_variants (deterministic). */
181 +export function variantKey(input: { grader?: string | null; grade?: string | null; qualifier?: string | null; condition?: string | null; completeness?: string | null; size?: string | null }): { key: string; label: string } {
182 + const parts: string[] = [];
183 + const labelParts: string[] = [];
184 + if (input.grader && input.grader !== 'raw' && input.grade) {
185 + parts.push(input.grader, input.grade);
186 + labelParts.push(`${getGrader(input.grader)?.name ?? input.grader.toUpperCase()} ${input.grade}`);
187 + if (input.qualifier) {
188 + parts.push(input.qualifier.toLowerCase().replace(/\s+/g, '_'));
189 + labelParts.push(input.qualifier);
190 + }
191 + } else {
192 + parts.push('raw');
193 + if (input.completeness) {
194 + parts.push(input.completeness);
195 + labelParts.push(humanize(input.completeness));
196 + } else if (input.condition) {
197 + parts.push(input.condition);
198 + labelParts.push(humanize(input.condition));
199 + } else {
200 + labelParts.push('Raw / ungraded');
201 + }
202 + }
203 + if (input.size) {
204 + parts.push(`size_${input.size.toLowerCase().replace(/\s+/g, '')}`);
205 + labelParts.push(`Size ${input.size}`);
206 + }
207 + return { key: parts.join('|'), label: labelParts.join(' · ') };
208 +}
209 +
210 +export function humanize(slug: string): string {
211 + return slug.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\bCib\b/, 'CIB');
212 +}
213 +
214 +// ---------- Indices ----------
215 +export interface IndexDef {
216 + ticker: string;
217 + name: string;
218 + flagship?: boolean;
219 + description?: string;
220 + families: string[];
221 + color: string;
222 + minConstituents: number;
223 +}
224 +export const INDEX_BASE_DATE: string = indicesJson.baseDate;
225 +export const INDEX_BASE_VALUE: number = indicesJson.baseValue;
226 +export const INDICES: readonly IndexDef[] = indicesJson.indices as IndexDef[];
227 +export function indexForCategory(categorySlug: string): IndexDef | undefined {
228 + const ticker = bySlug.get(categorySlug)?.index;
229 + return ticker ? INDICES.find((i) => i.ticker === ticker) : undefined;
230 +}
added packages/taxonomy/src/taxonomy.test.ts +35 −0
@@ -0,0 +1,35 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { CATEGORIES, FAMILIES, getCategory, normalizeCondition, parseGradeFromTitle, variantKey, indexForCategory, descendants } from './index.js';
3 +
4 +describe('taxonomy', () => {
5 + it('loads a large, consistent tree', () => {
6 + expect(FAMILIES.length).toBeGreaterThan(90);
7 + expect(CATEGORIES.length).toBeGreaterThan(FAMILIES.length);
8 + const slugs = new Set(CATEGORIES.map((c) => c.slug));
9 + expect(slugs.size).toBe(CATEGORIES.length);
10 + expect(getCategory('pokemon')?.familySlug).toBe('trading_cards');
11 + expect(getCategory('pokemon')?.index).toBe('RARE-TCG');
12 + expect(descendants('trading_cards')).toContain('pokemon');
13 + });
14 + it('normalises conditions per scale', () => {
15 + expect(normalizeCondition('pokemon', 'Near Mint')).toBe('near_mint');
16 + expect(normalizeCondition('nintendo_games', 'Complete In Box')).toBe('cib');
17 + expect(normalizeCondition('nike_jordan', 'DS')).toBe('deadstock');
18 + expect(normalizeCondition('rolex', 'Unworn')).toBe('unworn');
19 + expect(normalizeCondition('rolex', 'weird')).toBeNull();
20 + });
21 + it('parses grades', () => {
22 + expect(parseGradeFromTitle('1999 Pokemon Base Charizard 1st PSA 10')).toMatchObject({ grader: 'psa', grade: '10' });
23 + expect(parseGradeFromTitle('Charizard BGS 9.5 Black Label')).toMatchObject({ grader: 'bgs', grade: '9.5', qualifier: 'Black Label' });
24 + expect(parseGradeFromTitle('Amazing Fantasy 15 CGC 3.0')).toMatchObject({ grader: 'cgc', grade: '3.0' });
25 + expect(parseGradeFromTitle('Super Mario 64 WATA 9.8 A++')).toMatchObject({ grader: 'wata', grade: '9.8' });
26 + expect(parseGradeFromTitle('Rolex Daytona 116500LN')).toMatchObject({ grader: null, grade: null });
27 + expect(parseGradeFromTitle('1952 Topps Mantle raw')).toMatchObject({ grader: 'raw' });
28 + });
29 + it('builds variant keys', () => {
30 + expect(variantKey({ grader: 'psa', grade: '10' })).toEqual({ key: 'psa|10', label: 'PSA 10' });
31 + expect(variantKey({ completeness: 'sealed' })).toEqual({ key: 'raw|sealed', label: 'Sealed' });
32 + expect(variantKey({ size: '10' }).key).toBe('raw|size_10');
33 + expect(indexForCategory('rolex')?.ticker).toBe('RARE-WATCH');
34 + });
35 +});
added packages/taxonomy/tsconfig.json +9 −0
@@ -0,0 +1,9 @@
1 +{
2 + "extends": "../../tsconfig.base.json",
3 + "compilerOptions": {
4 + "rootDir": "../..",
5 + "outDir": "dist",
6 + "noEmit": true
7 + },
8 + "include": ["src", "../../data/taxonomy/*.json"]
9 +}
added packages/taxonomy/vitest.config.ts +5 −0
@@ -0,0 +1,5 @@
1 +import { defineConfig } from 'vitest/config';
2 +
3 +export default defineConfig({
4 + test: { include: ['src/**/*.test.ts'] },
5 +});
added pnpm-lock.yaml +6244 −0
@@ -0,0 +1,6244 @@
1 +lockfileVersion: '9.0'
2 +
3 +settings:
4 + autoInstallPeers: true
5 + excludeLinksFromLockfile: false
6 +
7 +importers:
8 +
9 + .:
10 + devDependencies:
11 + '@types/node':
12 + specifier: ^24.0.0
13 + version: 24.13.3
14 + tsx:
15 + specifier: ^4.20.0
16 + version: 4.23.13
17 + typescript:
18 + specifier: ^5.9.3
19 + version: 5.9.3
20 + vitest:
21 + specifier: ^3.2.0
22 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
23 +
24 + apps/api:
25 + dependencies:
26 + '@rareindex/shared':
27 + specifier: workspace:*
28 + version: link:../../packages/shared
29 + devDependencies:
30 + '@types/node':
31 + specifier: ^24.0.0
32 + version: 24.13.3
33 + typescript:
34 + specifier: ^5.9.3
35 + version: 5.9.3
36 + vitest:
37 + specifier: ^3.2.0
38 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
39 +
40 + apps/web:
41 + dependencies:
42 + '@rareindex/connectors':
43 + specifier: workspace:*
44 + version: link:../../packages/connectors
45 + '@rareindex/database':
46 + specifier: workspace:*
47 + version: link:../../packages/database
48 + '@rareindex/shared':
49 + specifier: workspace:*
50 + version: link:../../packages/shared
51 + '@rareindex/taxonomy':
52 + specifier: workspace:*
53 + version: link:../../packages/taxonomy
54 + '@tanstack/react-query':
55 + specifier: ^5.90.0
56 + version: 5.102.8(react@19.2.8)
57 + lucide-react:
58 + specifier: ^1.0.0
59 + version: 1.41.0(react@19.2.8)
60 + next:
61 + specifier: 16.3.4
62 + version: 16.3.4(@babel/core@7.29.7)(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
63 + react:
64 + specifier: 19.2.8
65 + version: 19.2.8
66 + react-dom:
67 + specifier: 19.2.8
68 + version: 19.2.8(react@19.2.8)
69 + server-only:
70 + specifier: ^0.0.1
71 + version: 0.0.1
72 + zod:
73 + specifier: ^4.0.0
74 + version: 4.5.4
75 + devDependencies:
76 + '@tailwindcss/postcss':
77 + specifier: ^4
78 + version: 4.3.3
79 + '@types/node':
80 + specifier: ^24.0.0
81 + version: 24.13.3
82 + '@types/react':
83 + specifier: ^19
84 + version: 19.2.18
85 + '@types/react-dom':
86 + specifier: ^19
87 + version: 19.2.7(@types/react@19.2.18)
88 + eslint:
89 + specifier: ^9
90 + version: 9.39.5(jiti@2.7.0)
91 + eslint-config-next:
92 + specifier: 16.3.4
93 + version: 16.3.4(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
94 + tailwindcss:
95 + specifier: ^4
96 + version: 4.3.3
97 + typescript:
98 + specifier: ^5.9.3
99 + version: 5.9.3
100 + vitest:
101 + specifier: ^3.2.0
102 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
103 +
104 + connectors:
105 + dependencies:
106 + '@rareindex/connectors':
107 + specifier: workspace:*
108 + version: link:../packages/connectors
109 + '@rareindex/shared':
110 + specifier: workspace:*
111 + version: link:../packages/shared
112 + '@rareindex/taxonomy':
113 + specifier: workspace:*
114 + version: link:../packages/taxonomy
115 + zod:
116 + specifier: ^4.0.0
117 + version: 4.5.4
118 + devDependencies:
119 + '@types/node':
120 + specifier: ^24.0.0
121 + version: 24.13.3
122 + typescript:
123 + specifier: ^5.9.3
124 + version: 5.9.3
125 + vitest:
126 + specifier: ^3.2.0
127 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
128 +
129 + packages/connectors:
130 + dependencies:
131 + '@rareindex/shared':
132 + specifier: workspace:*
133 + version: link:../shared
134 + '@rareindex/taxonomy':
135 + specifier: workspace:*
136 + version: link:../taxonomy
137 + cheerio:
138 + specifier: ^1.1.0
139 + version: 1.2.0
140 + zod:
141 + specifier: ^4.0.0
142 + version: 4.5.4
143 + devDependencies:
144 + '@types/node':
145 + specifier: ^24.0.0
146 + version: 24.13.3
147 + typescript:
148 + specifier: ^5.9.3
149 + version: 5.9.3
150 + vitest:
151 + specifier: ^3.2.0
152 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
153 +
154 + packages/database:
155 + dependencies:
156 + '@rareindex/shared':
157 + specifier: workspace:*
158 + version: link:../shared
159 + '@rareindex/taxonomy':
160 + specifier: workspace:*
161 + version: link:../taxonomy
162 + drizzle-orm:
163 + specifier: ^0.45.0
164 + version: 0.45.2(postgres@3.4.9)
165 + postgres:
166 + specifier: ^3.4.7
167 + version: 3.4.9
168 + devDependencies:
169 + '@types/node':
170 + specifier: ^24.0.0
171 + version: 24.13.3
172 + drizzle-kit:
173 + specifier: ^0.31.0
174 + version: 0.31.10
175 + tsx:
176 + specifier: ^4.20.0
177 + version: 4.23.13
178 + typescript:
179 + specifier: ^5.9.3
180 + version: 5.9.3
181 + vitest:
182 + specifier: ^3.2.0
183 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
184 +
185 + packages/shared:
186 + dependencies:
187 + pino:
188 + specifier: ^9.7.0
189 + version: 9.14.0
190 + zod:
191 + specifier: ^4.0.0
192 + version: 4.5.4
193 + devDependencies:
194 + '@types/node':
195 + specifier: ^24.0.0
196 + version: 24.13.3
197 + typescript:
198 + specifier: ^5.9.3
199 + version: 5.9.3
200 + vitest:
201 + specifier: ^3.2.0
202 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
203 +
204 + packages/taxonomy:
205 + dependencies:
206 + '@rareindex/shared':
207 + specifier: workspace:*
208 + version: link:../shared
209 + zod:
210 + specifier: ^4.0.0
211 + version: 4.5.4
212 + devDependencies:
213 + '@types/node':
214 + specifier: ^24.0.0
215 + version: 24.13.3
216 + typescript:
217 + specifier: ^5.9.3
218 + version: 5.9.3
219 + vitest:
220 + specifier: ^3.2.0
221 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
222 +
223 + workers:
224 + dependencies:
225 + '@rareindex/shared':
226 + specifier: workspace:*
227 + version: link:../packages/shared
228 + devDependencies:
229 + '@types/node':
230 + specifier: ^24.0.0
231 + version: 24.13.3
232 + typescript:
233 + specifier: ^5.9.3
234 + version: 5.9.3
235 + vitest:
236 + specifier: ^3.2.0
237 + version: 3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
238 +
239 +packages:
240 +
241 + '@alloc/quick-lru@5.3.0':
242 + resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
243 + engines: {node: '>=10'}
244 +
245 + '@babel/code-frame@7.29.7':
246 + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
247 + engines: {node: '>=6.9.0'}
248 +
249 + '@babel/compat-data@7.29.7':
250 + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
251 + engines: {node: '>=6.9.0'}
252 +
253 + '@babel/core@7.29.7':
254 + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
255 + engines: {node: '>=6.9.0'}
256 +
257 + '@babel/generator@7.29.8':
258 + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
259 + engines: {node: '>=6.9.0'}
260 +
261 + '@babel/helper-compilation-targets@7.29.7':
262 + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
263 + engines: {node: '>=6.9.0'}
264 +
265 + '@babel/helper-globals@7.29.7':
266 + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
267 + engines: {node: '>=6.9.0'}
268 +
269 + '@babel/helper-module-imports@7.29.7':
270 + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
271 + engines: {node: '>=6.9.0'}
272 +
273 + '@babel/helper-module-transforms@7.29.7':
274 + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
275 + engines: {node: '>=6.9.0'}
276 + peerDependencies:
277 + '@babel/core': ^7.0.0
278 +
279 + '@babel/helper-string-parser@7.29.7':
280 + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
281 + engines: {node: '>=6.9.0'}
282 +
283 + '@babel/helper-validator-identifier@7.29.7':
284 + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
285 + engines: {node: '>=6.9.0'}
286 +
287 + '@babel/helper-validator-option@7.29.7':
288 + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
289 + engines: {node: '>=6.9.0'}
290 +
291 + '@babel/helpers@7.29.7':
292 + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
293 + engines: {node: '>=6.9.0'}
294 +
295 + '@babel/parser@7.29.8':
296 + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
297 + engines: {node: '>=6.0.0'}
298 + hasBin: true
299 +
300 + '@babel/template@7.29.7':
301 + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
302 + engines: {node: '>=6.9.0'}
303 +
304 + '@babel/traverse@7.29.8':
305 + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==}
306 + engines: {node: '>=6.9.0'}
307 +
308 + '@babel/types@7.29.8':
309 + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
310 + engines: {node: '>=6.9.0'}
311 +
312 + '@drizzle-team/brocli@0.10.2':
313 + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
314 +
315 + '@emnapi/core@1.10.0':
316 + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
317 +
318 + '@emnapi/runtime@1.10.0':
319 + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
320 +
321 + '@emnapi/runtime@1.11.3':
322 + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
323 +
324 + '@emnapi/wasi-threads@1.2.1':
325 + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
326 +
327 + '@esbuild-kit/core-utils@3.3.2':
328 + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
329 + deprecated: 'Merged into tsx: https://tsx.hirok.io'
330 +
331 + '@esbuild-kit/esm-loader@2.6.5':
332 + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
333 + deprecated: 'Merged into tsx: https://tsx.hirok.io'
334 +
335 + '@esbuild/aix-ppc64@0.25.12':
336 + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
337 + engines: {node: '>=18'}
338 + cpu: [ppc64]
339 + os: [aix]
340 +
341 + '@esbuild/aix-ppc64@0.28.2':
342 + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
343 + engines: {node: '>=18'}
344 + cpu: [ppc64]
345 + os: [aix]
346 +
347 + '@esbuild/android-arm64@0.18.20':
348 + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
349 + engines: {node: '>=12'}
350 + cpu: [arm64]
351 + os: [android]
352 +
353 + '@esbuild/android-arm64@0.25.12':
354 + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
355 + engines: {node: '>=18'}
356 + cpu: [arm64]
357 + os: [android]
358 +
359 + '@esbuild/android-arm64@0.28.2':
360 + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
361 + engines: {node: '>=18'}
362 + cpu: [arm64]
363 + os: [android]
364 +
365 + '@esbuild/android-arm@0.18.20':
366 + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
367 + engines: {node: '>=12'}
368 + cpu: [arm]
369 + os: [android]
370 +
371 + '@esbuild/android-arm@0.25.12':
372 + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
373 + engines: {node: '>=18'}
374 + cpu: [arm]
375 + os: [android]
376 +
377 + '@esbuild/android-arm@0.28.2':
378 + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
379 + engines: {node: '>=18'}
380 + cpu: [arm]
381 + os: [android]
382 +
383 + '@esbuild/android-x64@0.18.20':
384 + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
385 + engines: {node: '>=12'}
386 + cpu: [x64]
387 + os: [android]
388 +
389 + '@esbuild/android-x64@0.25.12':
390 + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
391 + engines: {node: '>=18'}
392 + cpu: [x64]
393 + os: [android]
394 +
395 + '@esbuild/android-x64@0.28.2':
396 + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
397 + engines: {node: '>=18'}
398 + cpu: [x64]
399 + os: [android]
400 +
401 + '@esbuild/darwin-arm64@0.18.20':
402 + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
403 + engines: {node: '>=12'}
404 + cpu: [arm64]
405 + os: [darwin]
406 +
407 + '@esbuild/darwin-arm64@0.25.12':
408 + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
409 + engines: {node: '>=18'}
410 + cpu: [arm64]
411 + os: [darwin]
412 +
413 + '@esbuild/darwin-arm64@0.28.2':
414 + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
415 + engines: {node: '>=18'}
416 + cpu: [arm64]
417 + os: [darwin]
418 +
419 + '@esbuild/darwin-x64@0.18.20':
420 + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
421 + engines: {node: '>=12'}
422 + cpu: [x64]
423 + os: [darwin]
424 +
425 + '@esbuild/darwin-x64@0.25.12':
426 + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
427 + engines: {node: '>=18'}
428 + cpu: [x64]
429 + os: [darwin]
430 +
431 + '@esbuild/darwin-x64@0.28.2':
432 + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
433 + engines: {node: '>=18'}
434 + cpu: [x64]
435 + os: [darwin]
436 +
437 + '@esbuild/freebsd-arm64@0.18.20':
438 + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
439 + engines: {node: '>=12'}
440 + cpu: [arm64]
441 + os: [freebsd]
442 +
443 + '@esbuild/freebsd-arm64@0.25.12':
444 + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
445 + engines: {node: '>=18'}
446 + cpu: [arm64]
447 + os: [freebsd]
448 +
449 + '@esbuild/freebsd-arm64@0.28.2':
450 + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
451 + engines: {node: '>=18'}
452 + cpu: [arm64]
453 + os: [freebsd]
454 +
455 + '@esbuild/freebsd-x64@0.18.20':
456 + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
457 + engines: {node: '>=12'}
458 + cpu: [x64]
459 + os: [freebsd]
460 +
461 + '@esbuild/freebsd-x64@0.25.12':
462 + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
463 + engines: {node: '>=18'}
464 + cpu: [x64]
465 + os: [freebsd]
466 +
467 + '@esbuild/freebsd-x64@0.28.2':
468 + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
469 + engines: {node: '>=18'}
470 + cpu: [x64]
471 + os: [freebsd]
472 +
473 + '@esbuild/linux-arm64@0.18.20':
474 + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
475 + engines: {node: '>=12'}
476 + cpu: [arm64]
477 + os: [linux]
478 +
479 + '@esbuild/linux-arm64@0.25.12':
480 + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
481 + engines: {node: '>=18'}
482 + cpu: [arm64]
483 + os: [linux]
484 +
485 + '@esbuild/linux-arm64@0.28.2':
486 + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
487 + engines: {node: '>=18'}
488 + cpu: [arm64]
489 + os: [linux]
490 +
491 + '@esbuild/linux-arm@0.18.20':
492 + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
493 + engines: {node: '>=12'}
494 + cpu: [arm]
495 + os: [linux]
496 +
497 + '@esbuild/linux-arm@0.25.12':
498 + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
499 + engines: {node: '>=18'}
500 + cpu: [arm]
501 + os: [linux]
502 +
503 + '@esbuild/linux-arm@0.28.2':
504 + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
505 + engines: {node: '>=18'}
506 + cpu: [arm]
507 + os: [linux]
508 +
509 + '@esbuild/linux-ia32@0.18.20':
510 + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
511 + engines: {node: '>=12'}
512 + cpu: [ia32]
513 + os: [linux]
514 +
515 + '@esbuild/linux-ia32@0.25.12':
516 + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
517 + engines: {node: '>=18'}
518 + cpu: [ia32]
519 + os: [linux]
520 +
521 + '@esbuild/linux-ia32@0.28.2':
522 + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
523 + engines: {node: '>=18'}
524 + cpu: [ia32]
525 + os: [linux]
526 +
527 + '@esbuild/linux-loong64@0.18.20':
528 + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
529 + engines: {node: '>=12'}
530 + cpu: [loong64]
531 + os: [linux]
532 +
533 + '@esbuild/linux-loong64@0.25.12':
534 + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
535 + engines: {node: '>=18'}
536 + cpu: [loong64]
537 + os: [linux]
538 +
539 + '@esbuild/linux-loong64@0.28.2':
540 + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
541 + engines: {node: '>=18'}
542 + cpu: [loong64]
543 + os: [linux]
544 +
545 + '@esbuild/linux-mips64el@0.18.20':
546 + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
547 + engines: {node: '>=12'}
548 + cpu: [mips64el]
549 + os: [linux]
550 +
551 + '@esbuild/linux-mips64el@0.25.12':
552 + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
553 + engines: {node: '>=18'}
554 + cpu: [mips64el]
555 + os: [linux]
556 +
557 + '@esbuild/linux-mips64el@0.28.2':
558 + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
559 + engines: {node: '>=18'}
560 + cpu: [mips64el]
561 + os: [linux]
562 +
563 + '@esbuild/linux-ppc64@0.18.20':
564 + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
565 + engines: {node: '>=12'}
566 + cpu: [ppc64]
567 + os: [linux]
568 +
569 + '@esbuild/linux-ppc64@0.25.12':
570 + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
571 + engines: {node: '>=18'}
572 + cpu: [ppc64]
573 + os: [linux]
574 +
575 + '@esbuild/linux-ppc64@0.28.2':
576 + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
577 + engines: {node: '>=18'}
578 + cpu: [ppc64]
579 + os: [linux]
580 +
581 + '@esbuild/linux-riscv64@0.18.20':
582 + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
583 + engines: {node: '>=12'}
584 + cpu: [riscv64]
585 + os: [linux]
586 +
587 + '@esbuild/linux-riscv64@0.25.12':
588 + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
589 + engines: {node: '>=18'}
590 + cpu: [riscv64]
591 + os: [linux]
592 +
593 + '@esbuild/linux-riscv64@0.28.2':
594 + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
595 + engines: {node: '>=18'}
596 + cpu: [riscv64]
597 + os: [linux]
598 +
599 + '@esbuild/linux-s390x@0.18.20':
600 + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
601 + engines: {node: '>=12'}
602 + cpu: [s390x]
603 + os: [linux]
604 +
605 + '@esbuild/linux-s390x@0.25.12':
606 + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
607 + engines: {node: '>=18'}
608 + cpu: [s390x]
609 + os: [linux]
610 +
611 + '@esbuild/linux-s390x@0.28.2':
612 + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
613 + engines: {node: '>=18'}
614 + cpu: [s390x]
615 + os: [linux]
616 +
617 + '@esbuild/linux-x64@0.18.20':
618 + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
619 + engines: {node: '>=12'}
620 + cpu: [x64]
621 + os: [linux]
622 +
623 + '@esbuild/linux-x64@0.25.12':
624 + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
625 + engines: {node: '>=18'}
626 + cpu: [x64]
627 + os: [linux]
628 +
629 + '@esbuild/linux-x64@0.28.2':
630 + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
631 + engines: {node: '>=18'}
632 + cpu: [x64]
633 + os: [linux]
634 +
635 + '@esbuild/netbsd-arm64@0.25.12':
636 + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
637 + engines: {node: '>=18'}
638 + cpu: [arm64]
639 + os: [netbsd]
640 +
641 + '@esbuild/netbsd-arm64@0.28.2':
642 + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
643 + engines: {node: '>=18'}
644 + cpu: [arm64]
645 + os: [netbsd]
646 +
647 + '@esbuild/netbsd-x64@0.18.20':
648 + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
649 + engines: {node: '>=12'}
650 + cpu: [x64]
651 + os: [netbsd]
652 +
653 + '@esbuild/netbsd-x64@0.25.12':
654 + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
655 + engines: {node: '>=18'}
656 + cpu: [x64]
657 + os: [netbsd]
658 +
659 + '@esbuild/netbsd-x64@0.28.2':
660 + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
661 + engines: {node: '>=18'}
662 + cpu: [x64]
663 + os: [netbsd]
664 +
665 + '@esbuild/openbsd-arm64@0.25.12':
666 + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
667 + engines: {node: '>=18'}
668 + cpu: [arm64]
669 + os: [openbsd]
670 +
671 + '@esbuild/openbsd-arm64@0.28.2':
672 + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
673 + engines: {node: '>=18'}
674 + cpu: [arm64]
675 + os: [openbsd]
676 +
677 + '@esbuild/openbsd-x64@0.18.20':
678 + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
679 + engines: {node: '>=12'}
680 + cpu: [x64]
681 + os: [openbsd]
682 +
683 + '@esbuild/openbsd-x64@0.25.12':
684 + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
685 + engines: {node: '>=18'}
686 + cpu: [x64]
687 + os: [openbsd]
688 +
689 + '@esbuild/openbsd-x64@0.28.2':
690 + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
691 + engines: {node: '>=18'}
692 + cpu: [x64]
693 + os: [openbsd]
694 +
695 + '@esbuild/openharmony-arm64@0.25.12':
696 + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
697 + engines: {node: '>=18'}
698 + cpu: [arm64]
699 + os: [openharmony]
700 +
701 + '@esbuild/openharmony-arm64@0.28.2':
702 + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
703 + engines: {node: '>=18'}
704 + cpu: [arm64]
705 + os: [openharmony]
706 +
707 + '@esbuild/sunos-x64@0.18.20':
708 + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
709 + engines: {node: '>=12'}
710 + cpu: [x64]
711 + os: [sunos]
712 +
713 + '@esbuild/sunos-x64@0.25.12':
714 + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
715 + engines: {node: '>=18'}
716 + cpu: [x64]
717 + os: [sunos]
718 +
719 + '@esbuild/sunos-x64@0.28.2':
720 + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
721 + engines: {node: '>=18'}
722 + cpu: [x64]
723 + os: [sunos]
724 +
725 + '@esbuild/win32-arm64@0.18.20':
726 + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
727 + engines: {node: '>=12'}
728 + cpu: [arm64]
729 + os: [win32]
730 +
731 + '@esbuild/win32-arm64@0.25.12':
732 + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
733 + engines: {node: '>=18'}
734 + cpu: [arm64]
735 + os: [win32]
736 +
737 + '@esbuild/win32-arm64@0.28.2':
738 + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
739 + engines: {node: '>=18'}
740 + cpu: [arm64]
741 + os: [win32]
742 +
743 + '@esbuild/win32-ia32@0.18.20':
744 + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
745 + engines: {node: '>=12'}
746 + cpu: [ia32]
747 + os: [win32]
748 +
749 + '@esbuild/win32-ia32@0.25.12':
750 + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
751 + engines: {node: '>=18'}
752 + cpu: [ia32]
753 + os: [win32]
754 +
755 + '@esbuild/win32-ia32@0.28.2':
756 + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
757 + engines: {node: '>=18'}
758 + cpu: [ia32]
759 + os: [win32]
760 +
761 + '@esbuild/win32-x64@0.18.20':
762 + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
763 + engines: {node: '>=12'}
764 + cpu: [x64]
765 + os: [win32]
766 +
767 + '@esbuild/win32-x64@0.25.12':
768 + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
769 + engines: {node: '>=18'}
770 + cpu: [x64]
771 + os: [win32]
772 +
773 + '@esbuild/win32-x64@0.28.2':
774 + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
775 + engines: {node: '>=18'}
776 + cpu: [x64]
777 + os: [win32]
778 +
779 + '@eslint-community/eslint-utils@4.10.1':
780 + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
781 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
782 + peerDependencies:
783 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
784 +
785 + '@eslint-community/eslint-utils@4.9.1':
786 + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
787 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
788 + peerDependencies:
789 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
790 +
791 + '@eslint-community/regexpp@4.12.2':
792 + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
793 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
794 +
795 + '@eslint/config-array@0.21.2':
796 + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
797 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
798 +
799 + '@eslint/config-helpers@0.4.2':
800 + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
801 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
802 +
803 + '@eslint/core@0.17.0':
804 + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
805 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
806 +
807 + '@eslint/eslintrc@3.3.7':
808 + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==}
809 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
810 +
811 + '@eslint/js@9.39.5':
812 + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==}
813 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
814 +
815 + '@eslint/object-schema@2.1.7':
816 + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
817 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
818 +
819 + '@eslint/plugin-kit@0.4.1':
820 + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
821 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
822 +
823 + '@humanfs/core@0.19.2':
824 + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
825 + engines: {node: '>=18.18.0'}
826 +
827 + '@humanfs/node@0.16.8':
828 + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
829 + engines: {node: '>=18.18.0'}
830 +
831 + '@humanfs/types@0.15.0':
832 + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
833 + engines: {node: '>=18.18.0'}
834 +
835 + '@humanwhocodes/module-importer@1.0.1':
836 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
837 + engines: {node: '>=12.22'}
838 +
839 + '@humanwhocodes/retry@0.4.3':
840 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
841 + engines: {node: '>=18.18'}
842 +
843 + '@img/colour@1.1.0':
844 + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
845 + engines: {node: '>=18'}
846 +
847 + '@img/sharp-darwin-arm64@0.35.4':
848 + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
849 + engines: {node: '>=20.9.0'}
850 + cpu: [arm64]
851 + os: [darwin]
852 +
853 + '@img/sharp-darwin-x64@0.35.4':
854 + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
855 + engines: {node: '>=20.9.0'}
856 + cpu: [x64]
857 + os: [darwin]
858 +
859 + '@img/sharp-freebsd-wasm32@0.35.4':
860 + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
861 + engines: {node: '>=20.9.0'}
862 + os: [freebsd]
863 +
864 + '@img/sharp-libvips-darwin-arm64@1.3.3':
865 + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
866 + cpu: [arm64]
867 + os: [darwin]
868 +
869 + '@img/sharp-libvips-darwin-x64@1.3.3':
870 + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
871 + cpu: [x64]
872 + os: [darwin]
873 +
874 + '@img/sharp-libvips-linux-arm64@1.3.3':
875 + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
876 + cpu: [arm64]
877 + os: [linux]
878 + libc: [glibc]
879 +
880 + '@img/sharp-libvips-linux-arm@1.3.3':
881 + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
882 + cpu: [arm]
883 + os: [linux]
884 + libc: [glibc]
885 +
886 + '@img/sharp-libvips-linux-ppc64@1.3.3':
887 + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
888 + cpu: [ppc64]
889 + os: [linux]
890 + libc: [glibc]
891 +
892 + '@img/sharp-libvips-linux-riscv64@1.3.3':
893 + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
894 + cpu: [riscv64]
895 + os: [linux]
896 + libc: [glibc]
897 +
898 + '@img/sharp-libvips-linux-s390x@1.3.3':
899 + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
900 + cpu: [s390x]
901 + os: [linux]
902 + libc: [glibc]
903 +
904 + '@img/sharp-libvips-linux-x64@1.3.3':
905 + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
906 + cpu: [x64]
907 + os: [linux]
908 + libc: [glibc]
909 +
910 + '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
911 + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
912 + cpu: [arm64]
913 + os: [linux]
914 + libc: [musl]
915 +
916 + '@img/sharp-libvips-linuxmusl-x64@1.3.3':
917 + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
918 + cpu: [x64]
919 + os: [linux]
920 + libc: [musl]
921 +
922 + '@img/sharp-linux-arm64@0.35.4':
923 + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
924 + engines: {node: '>=20.9.0'}
925 + cpu: [arm64]
926 + os: [linux]
927 + libc: [glibc]
928 +
929 + '@img/sharp-linux-arm@0.35.4':
930 + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
931 + engines: {node: '>=20.9.0'}
932 + cpu: [arm]
933 + os: [linux]
934 + libc: [glibc]
935 +
936 + '@img/sharp-linux-ppc64@0.35.4':
937 + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
938 + engines: {node: '>=20.9.0'}
939 + cpu: [ppc64]
940 + os: [linux]
941 + libc: [glibc]
942 +
943 + '@img/sharp-linux-riscv64@0.35.4':
944 + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
945 + engines: {node: '>=20.9.0'}
946 + cpu: [riscv64]
947 + os: [linux]
948 + libc: [glibc]
949 +
950 + '@img/sharp-linux-s390x@0.35.4':
951 + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
952 + engines: {node: '>=20.9.0'}
953 + cpu: [s390x]
954 + os: [linux]
955 + libc: [glibc]
956 +
957 + '@img/sharp-linux-x64@0.35.4':
958 + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
959 + engines: {node: '>=20.9.0'}
960 + cpu: [x64]
961 + os: [linux]
962 + libc: [glibc]
963 +
964 + '@img/sharp-linuxmusl-arm64@0.35.4':
965 + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
966 + engines: {node: '>=20.9.0'}
967 + cpu: [arm64]
968 + os: [linux]
969 + libc: [musl]
970 +
971 + '@img/sharp-linuxmusl-x64@0.35.4':
972 + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
973 + engines: {node: '>=20.9.0'}
974 + cpu: [x64]
975 + os: [linux]
976 + libc: [musl]
977 +
978 + '@img/sharp-wasm32@0.35.4':
979 + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
980 + engines: {node: '>=20.9.0'}
981 +
982 + '@img/sharp-webcontainers-wasm32@0.35.4':
983 + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
984 + engines: {node: '>=20.9.0'}
985 + cpu: [wasm32]
986 +
987 + '@img/sharp-win32-arm64@0.35.4':
988 + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
989 + engines: {node: '>=20.9.0'}
990 + cpu: [arm64]
991 + os: [win32]
992 +
993 + '@img/sharp-win32-ia32@0.35.4':
994 + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
995 + engines: {node: ^20.9.0}
996 + cpu: [ia32]
997 + os: [win32]
998 +
999 + '@img/sharp-win32-x64@0.35.4':
1000 + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
1001 + engines: {node: '>=20.9.0'}
1002 + cpu: [x64]
1003 + os: [win32]
1004 +
1005 + '@jridgewell/gen-mapping@0.3.13':
1006 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
1007 +
1008 + '@jridgewell/remapping@2.3.5':
1009 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
1010 +
1011 + '@jridgewell/resolve-uri@3.1.2':
1012 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
1013 + engines: {node: '>=6.0.0'}
1014 +
1015 + '@jridgewell/sourcemap-codec@1.6.0':
1016 + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
1017 +
1018 + '@jridgewell/trace-mapping@0.3.31':
1019 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
1020 +
1021 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
1022 + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
1023 + engines: {node: ^22.20 || ^24.12 || >=25}
1024 + cpu: [x64]
1025 + os: [linux]
1026 + libc: [glibc]
1027 +
1028 + '@napi-rs/wasm-runtime@1.2.3':
1029 + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==}
1030 + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
1031 + peerDependencies:
1032 + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
1033 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
1034 +
1035 + '@next/env@16.3.4':
1036 + resolution: {integrity: sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==}
1037 +
1038 + '@next/eslint-plugin-next@16.3.4':
1039 + resolution: {integrity: sha512-szW9y2Aumu4z88YXfTzcFsgUAg2k64uzbtcO5L9f1AKS4w/GUKJcbFllRflROVyNPgJtGOnvNxiyp3v6b+prIA==}
1040 +
1041 + '@next/swc-darwin-arm64@16.3.4':
1042 + resolution: {integrity: sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw==}
1043 + engines: {node: '>= 10'}
1044 + cpu: [arm64]
1045 + os: [darwin]
1046 +
1047 + '@next/swc-darwin-x64@16.3.4':
1048 + resolution: {integrity: sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg==}
1049 + engines: {node: '>= 10'}
1050 + cpu: [x64]
1051 + os: [darwin]
1052 +
1053 + '@next/swc-linux-arm64-gnu@16.3.4':
1054 + resolution: {integrity: sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA==}
1055 + engines: {node: '>= 10'}
1056 + cpu: [arm64]
1057 + os: [linux]
1058 + libc: [glibc]
1059 +
1060 + '@next/swc-linux-arm64-musl@16.3.4':
1061 + resolution: {integrity: sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg==}
1062 + engines: {node: '>= 10'}
1063 + cpu: [arm64]
1064 + os: [linux]
1065 + libc: [musl]
1066 +
1067 + '@next/swc-linux-x64-gnu@16.3.4':
1068 + resolution: {integrity: sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw==}
1069 + engines: {node: '>= 10'}
1070 + cpu: [x64]
1071 + os: [linux]
1072 + libc: [glibc]
1073 +
1074 + '@next/swc-linux-x64-musl@16.3.4':
1075 + resolution: {integrity: sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q==}
1076 + engines: {node: '>= 10'}
1077 + cpu: [x64]
1078 + os: [linux]
1079 + libc: [musl]
1080 +
1081 + '@next/swc-win32-arm64-msvc@16.3.4':
1082 + resolution: {integrity: sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A==}
1083 + engines: {node: '>= 10'}
1084 + cpu: [arm64]
1085 + os: [win32]
1086 +
1087 + '@next/swc-win32-x64-msvc@16.3.4':
1088 + resolution: {integrity: sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw==}
1089 + engines: {node: '>= 10'}
1090 + cpu: [x64]
1091 + os: [win32]
1092 +
1093 + '@nodelib/fs.scandir@2.1.5':
1094 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
1095 + engines: {node: '>= 8'}
1096 +
1097 + '@nodelib/fs.stat@2.0.5':
1098 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
1099 + engines: {node: '>= 8'}
1100 +
1101 + '@nodelib/fs.walk@1.2.8':
1102 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
1103 + engines: {node: '>= 8'}
1104 +
1105 + '@nolyfill/is-core-module@1.0.39':
1106 + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
1107 + engines: {node: '>=12.4.0'}
1108 +
1109 + '@pinojs/redact@0.4.0':
1110 + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
1111 +
1112 + '@rollup/rollup-android-arm-eabi@4.63.1':
1113 + resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==}
1114 + cpu: [arm]
1115 + os: [android]
1116 +
1117 + '@rollup/rollup-android-arm64@4.63.1':
1118 + resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==}
1119 + cpu: [arm64]
1120 + os: [android]
1121 +
1122 + '@rollup/rollup-darwin-arm64@4.63.1':
1123 + resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==}
1124 + cpu: [arm64]
1125 + os: [darwin]
1126 +
1127 + '@rollup/rollup-darwin-x64@4.63.1':
1128 + resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==}
1129 + cpu: [x64]
1130 + os: [darwin]
1131 +
1132 + '@rollup/rollup-freebsd-arm64@4.63.1':
1133 + resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==}
1134 + cpu: [arm64]
1135 + os: [freebsd]
1136 +
1137 + '@rollup/rollup-freebsd-x64@4.63.1':
1138 + resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==}
1139 + cpu: [x64]
1140 + os: [freebsd]
1141 +
1142 + '@rollup/rollup-linux-arm-gnueabihf@4.63.1':
1143 + resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==}
1144 + cpu: [arm]
1145 + os: [linux]
1146 + libc: [glibc]
1147 +
1148 + '@rollup/rollup-linux-arm-musleabihf@4.63.1':
1149 + resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==}
1150 + cpu: [arm]
1151 + os: [linux]
1152 + libc: [musl]
1153 +
1154 + '@rollup/rollup-linux-arm64-gnu@4.63.1':
1155 + resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==}
1156 + cpu: [arm64]
1157 + os: [linux]
1158 + libc: [glibc]
1159 +
1160 + '@rollup/rollup-linux-arm64-musl@4.63.1':
1161 + resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==}
1162 + cpu: [arm64]
1163 + os: [linux]
1164 + libc: [musl]
1165 +
1166 + '@rollup/rollup-linux-loong64-gnu@4.63.1':
1167 + resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==}
1168 + cpu: [loong64]
1169 + os: [linux]
1170 + libc: [glibc]
1171 +
1172 + '@rollup/rollup-linux-loong64-musl@4.63.1':
1173 + resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==}
1174 + cpu: [loong64]
1175 + os: [linux]
1176 + libc: [musl]
1177 +
1178 + '@rollup/rollup-linux-ppc64-gnu@4.63.1':
1179 + resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==}
1180 + cpu: [ppc64]
1181 + os: [linux]
1182 + libc: [glibc]
1183 +
1184 + '@rollup/rollup-linux-ppc64-musl@4.63.1':
1185 + resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==}
1186 + cpu: [ppc64]
1187 + os: [linux]
1188 + libc: [musl]
1189 +
1190 + '@rollup/rollup-linux-riscv64-gnu@4.63.1':
1191 + resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==}
1192 + cpu: [riscv64]
1193 + os: [linux]
1194 + libc: [glibc]
1195 +
1196 + '@rollup/rollup-linux-riscv64-musl@4.63.1':
1197 + resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==}
1198 + cpu: [riscv64]
1199 + os: [linux]
1200 + libc: [musl]
1201 +
1202 + '@rollup/rollup-linux-s390x-gnu@4.63.1':
1203 + resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==}
1204 + cpu: [s390x]
1205 + os: [linux]
1206 + libc: [glibc]
1207 +
1208 + '@rollup/rollup-linux-x64-gnu@4.63.1':
1209 + resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==}
1210 + cpu: [x64]
1211 + os: [linux]
1212 + libc: [glibc]
1213 +
1214 + '@rollup/rollup-linux-x64-musl@4.63.1':
1215 + resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==}
1216 + cpu: [x64]
1217 + os: [linux]
1218 + libc: [musl]
1219 +
1220 + '@rollup/rollup-openbsd-x64@4.63.1':
1221 + resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==}
1222 + cpu: [x64]
1223 + os: [openbsd]
1224 +
1225 + '@rollup/rollup-openharmony-arm64@4.63.1':
1226 + resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==}
1227 + cpu: [arm64]
1228 + os: [openharmony]
1229 +
1230 + '@rollup/rollup-win32-arm64-msvc@4.63.1':
1231 + resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==}
1232 + cpu: [arm64]
1233 + os: [win32]
1234 +
1235 + '@rollup/rollup-win32-ia32-msvc@4.63.1':
1236 + resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==}
1237 + cpu: [ia32]
1238 + os: [win32]
1239 +
1240 + '@rollup/rollup-win32-x64-gnu@4.63.1':
1241 + resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==}
1242 + cpu: [x64]
1243 + os: [win32]
1244 +
1245 + '@rollup/rollup-win32-x64-msvc@4.63.1':
1246 + resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==}
1247 + cpu: [x64]
1248 + os: [win32]
1249 +
1250 + '@rtsao/scc@1.1.0':
1251 + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
1252 +
1253 + '@swc/helpers@0.5.23':
1254 + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
1255 +
1256 + '@tailwindcss/node@4.3.3':
1257 + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
1258 +
1259 + '@tailwindcss/oxide-android-arm64@4.3.3':
1260 + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==}
1261 + engines: {node: '>= 20'}
1262 + cpu: [arm64]
1263 + os: [android]
1264 +
1265 + '@tailwindcss/oxide-darwin-arm64@4.3.3':
1266 + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==}
1267 + engines: {node: '>= 20'}
1268 + cpu: [arm64]
1269 + os: [darwin]
1270 +
1271 + '@tailwindcss/oxide-darwin-x64@4.3.3':
1272 + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==}
1273 + engines: {node: '>= 20'}
1274 + cpu: [x64]
1275 + os: [darwin]
1276 +
1277 + '@tailwindcss/oxide-freebsd-x64@4.3.3':
1278 + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==}
1279 + engines: {node: '>= 20'}
1280 + cpu: [x64]
1281 + os: [freebsd]
1282 +
1283 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3':
1284 + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==}
1285 + engines: {node: '>= 20'}
1286 + cpu: [arm]
1287 + os: [linux]
1288 +
1289 + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3':
1290 + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==}
1291 + engines: {node: '>= 20'}
1292 + cpu: [arm64]
1293 + os: [linux]
1294 + libc: [glibc]
1295 +
1296 + '@tailwindcss/oxide-linux-arm64-musl@4.3.3':
1297 + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==}
1298 + engines: {node: '>= 20'}
1299 + cpu: [arm64]
1300 + os: [linux]
1301 + libc: [musl]
1302 +
1303 + '@tailwindcss/oxide-linux-x64-gnu@4.3.3':
1304 + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==}
1305 + engines: {node: '>= 20'}
1306 + cpu: [x64]
1307 + os: [linux]
1308 + libc: [glibc]
1309 +
1310 + '@tailwindcss/oxide-linux-x64-musl@4.3.3':
1311 + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==}
1312 + engines: {node: '>= 20'}
1313 + cpu: [x64]
1314 + os: [linux]
1315 + libc: [musl]
1316 +
1317 + '@tailwindcss/oxide-wasm32-wasi@4.3.3':
1318 + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==}
1319 + engines: {node: '>=14.0.0'}
1320 + cpu: [wasm32]
1321 + bundledDependencies:
1322 + - '@napi-rs/wasm-runtime'
1323 + - '@emnapi/core'
1324 + - '@emnapi/runtime'
1325 + - '@tybys/wasm-util'
1326 + - '@emnapi/wasi-threads'
1327 + - tslib
1328 +
1329 + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3':
1330 + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==}
1331 + engines: {node: '>= 20'}
1332 + cpu: [arm64]
1333 + os: [win32]
1334 +
1335 + '@tailwindcss/oxide-win32-x64-msvc@4.3.3':
1336 + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==}
1337 + engines: {node: '>= 20'}
1338 + cpu: [x64]
1339 + os: [win32]
1340 +
1341 + '@tailwindcss/oxide@4.3.3':
1342 + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==}
1343 + engines: {node: '>= 20'}
1344 +
1345 + '@tailwindcss/postcss@4.3.3':
1346 + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==}
1347 +
1348 + '@tanstack/query-core@5.102.8':
1349 + resolution: {integrity: sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==}
1350 +
1351 + '@tanstack/react-query@5.102.8':
1352 + resolution: {integrity: sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==}
1353 + peerDependencies:
1354 + react: ^18 || ^19
1355 +
1356 + '@tybys/wasm-util@0.10.3':
1357 + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
1358 +
1359 + '@types/chai@5.2.3':
1360 + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
1361 +
1362 + '@types/deep-eql@4.0.2':
1363 + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
1364 +
1365 + '@types/estree@1.0.9':
1366 + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
1367 +
1368 + '@types/json-schema@7.0.15':
1369 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
1370 +
1371 + '@types/json5@0.0.29':
1372 + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
1373 +
1374 + '@types/node@24.13.3':
1375 + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
1376 +
1377 + '@types/react-dom@19.2.7':
1378 + resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==}
1379 + peerDependencies:
1380 + '@types/react': ^19.2.0
1381 +
1382 + '@types/react@19.2.18':
1383 + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==}
1384 +
1385 + '@typescript-eslint/eslint-plugin@8.69.0':
1386 + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==}
1387 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1388 + peerDependencies:
1389 + '@typescript-eslint/parser': ^8.69.0
1390 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1391 + typescript: '>=4.8.4 <6.1.0'
1392 +
1393 + '@typescript-eslint/parser@8.69.0':
1394 + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==}
1395 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1396 + peerDependencies:
1397 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1398 + typescript: '>=4.8.4 <6.1.0'
1399 +
1400 + '@typescript-eslint/project-service@8.69.0':
1401 + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==}
1402 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1403 + peerDependencies:
1404 + typescript: '>=4.8.4 <6.1.0'
1405 +
1406 + '@typescript-eslint/scope-manager@8.69.0':
1407 + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==}
1408 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1409 +
1410 + '@typescript-eslint/tsconfig-utils@8.69.0':
1411 + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==}
1412 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1413 + peerDependencies:
1414 + typescript: '>=4.8.4 <6.1.0'
1415 +
1416 + '@typescript-eslint/type-utils@8.69.0':
1417 + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==}
1418 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1419 + peerDependencies:
1420 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1421 + typescript: '>=4.8.4 <6.1.0'
1422 +
1423 + '@typescript-eslint/types@8.69.0':
1424 + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==}
1425 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1426 +
1427 + '@typescript-eslint/typescript-estree@8.69.0':
1428 + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==}
1429 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1430 + peerDependencies:
1431 + typescript: '>=4.8.4 <6.1.0'
1432 +
1433 + '@typescript-eslint/utils@8.69.0':
1434 + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==}
1435 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1436 + peerDependencies:
1437 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1438 + typescript: '>=4.8.4 <6.1.0'
1439 +
1440 + '@typescript-eslint/visitor-keys@8.69.0':
1441 + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==}
1442 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1443 +
1444 + '@unrs/resolver-binding-android-arm-eabi@1.12.2':
1445 + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==}
1446 + cpu: [arm]
1447 + os: [android]
1448 +
1449 + '@unrs/resolver-binding-android-arm64@1.12.2':
1450 + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==}
1451 + cpu: [arm64]
1452 + os: [android]
1453 +
1454 + '@unrs/resolver-binding-darwin-arm64@1.12.2':
1455 + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==}
1456 + cpu: [arm64]
1457 + os: [darwin]
1458 +
1459 + '@unrs/resolver-binding-darwin-x64@1.12.2':
1460 + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==}
1461 + cpu: [x64]
1462 + os: [darwin]
1463 +
1464 + '@unrs/resolver-binding-freebsd-x64@1.12.2':
1465 + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==}
1466 + cpu: [x64]
1467 + os: [freebsd]
1468 +
1469 + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
1470 + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==}
1471 + cpu: [arm]
1472 + os: [linux]
1473 +
1474 + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
1475 + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==}
1476 + cpu: [arm]
1477 + os: [linux]
1478 +
1479 + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
1480 + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==}
1481 + cpu: [arm64]
1482 + os: [linux]
1483 + libc: [glibc]
1484 +
1485 + '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
1486 + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==}
1487 + cpu: [arm64]
1488 + os: [linux]
1489 + libc: [musl]
1490 +
1491 + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
1492 + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==}
1493 + cpu: [loong64]
1494 + os: [linux]
1495 + libc: [glibc]
1496 +
1497 + '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
1498 + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==}
1499 + cpu: [loong64]
1500 + os: [linux]
1501 + libc: [musl]
1502 +
1503 + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
1504 + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==}
1505 + cpu: [ppc64]
1506 + os: [linux]
1507 + libc: [glibc]
1508 +
1509 + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
1510 + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==}
1511 + cpu: [riscv64]
1512 + os: [linux]
1513 + libc: [glibc]
1514 +
1515 + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
1516 + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==}
1517 + cpu: [riscv64]
1518 + os: [linux]
1519 + libc: [musl]
1520 +
1521 + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
1522 + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==}
1523 + cpu: [s390x]
1524 + os: [linux]
1525 + libc: [glibc]
1526 +
1527 + '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
1528 + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==}
1529 + cpu: [x64]
1530 + os: [linux]
1531 + libc: [glibc]
1532 +
1533 + '@unrs/resolver-binding-linux-x64-musl@1.12.2':
1534 + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==}
1535 + cpu: [x64]
1536 + os: [linux]
1537 + libc: [musl]
1538 +
1539 + '@unrs/resolver-binding-openharmony-arm64@1.12.2':
1540 + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==}
1541 + cpu: [arm64]
1542 + os: [openharmony]
1543 +
1544 + '@unrs/resolver-binding-wasm32-wasi@1.12.2':
1545 + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==}
1546 + engines: {node: '>=14.0.0'}
1547 + cpu: [wasm32]
1548 +
1549 + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
1550 + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==}
1551 + cpu: [arm64]
1552 + os: [win32]
1553 +
1554 + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
1555 + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==}
1556 + cpu: [ia32]
1557 + os: [win32]
1558 +
1559 + '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
1560 + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==}
1561 + cpu: [x64]
1562 + os: [win32]
1563 +
1564 + '@vitest/expect@3.2.7':
1565 + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==}
1566 +
1567 + '@vitest/mocker@3.2.7':
1568 + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==}
1569 + peerDependencies:
1570 + msw: ^2.4.9
1571 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
1572 + peerDependenciesMeta:
1573 + msw:
1574 + optional: true
1575 + vite:
1576 + optional: true
1577 +
1578 + '@vitest/pretty-format@3.2.7':
1579 + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==}
1580 +
1581 + '@vitest/runner@3.2.7':
1582 + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==}
1583 +
1584 + '@vitest/snapshot@3.2.7':
1585 + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==}
1586 +
1587 + '@vitest/spy@3.2.7':
1588 + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==}
1589 +
1590 + '@vitest/utils@3.2.7':
1591 + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
1592 +
1593 + acorn-jsx@5.3.2:
1594 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
1595 + peerDependencies:
1596 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
1597 +
1598 + acorn@8.18.0:
1599 + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
1600 + engines: {node: '>=0.4.0'}
1601 + hasBin: true
1602 +
1603 + ajv@6.15.0:
1604 + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
1605 +
1606 + ansi-styles@4.3.0:
1607 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
1608 + engines: {node: '>=8'}
1609 +
1610 + argparse@2.0.1:
1611 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
1612 +
1613 + aria-query@5.3.2:
1614 + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
1615 + engines: {node: '>= 0.4'}
1616 +
1617 + array-buffer-byte-length@1.0.2:
1618 + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
1619 + engines: {node: '>= 0.4'}
1620 +
1621 + array-includes@3.1.9:
1622 + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
1623 + engines: {node: '>= 0.4'}
1624 +
1625 + array.prototype.findlast@1.2.5:
1626 + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
1627 + engines: {node: '>= 0.4'}
1628 +
1629 + array.prototype.findlastindex@1.2.6:
1630 + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
1631 + engines: {node: '>= 0.4'}
1632 +
1633 + array.prototype.flat@1.3.3:
1634 + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
1635 + engines: {node: '>= 0.4'}
1636 +
1637 + array.prototype.flatmap@1.3.3:
1638 + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
1639 + engines: {node: '>= 0.4'}
1640 +
1641 + array.prototype.tosorted@1.1.4:
1642 + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
1643 + engines: {node: '>= 0.4'}
1644 +
1645 + arraybuffer.prototype.slice@1.0.4:
1646 + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
1647 + engines: {node: '>= 0.4'}
1648 +
1649 + assertion-error@2.0.1:
1650 + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
1651 + engines: {node: '>=12'}
1652 +
1653 + ast-types-flow@0.0.8:
1654 + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
1655 +
1656 + async-function@1.0.0:
1657 + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
1658 + engines: {node: '>= 0.4'}
1659 +
1660 + atomic-sleep@1.0.0:
1661 + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
1662 + engines: {node: '>=8.0.0'}
1663 +
1664 + available-typed-arrays@1.0.7:
1665 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
1666 + engines: {node: '>= 0.4'}
1667 +
1668 + axe-core@4.13.0:
1669 + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==}
1670 + engines: {node: '>=4'}
1671 +
1672 + axobject-query@4.1.0:
1673 + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
1674 + engines: {node: '>= 0.4'}
1675 +
1676 + balanced-match@1.0.2:
1677 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
1678 +
1679 + balanced-match@4.0.4:
1680 + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
1681 + engines: {node: 18 || 20 || >=22}
1682 +
1683 + baseline-browser-mapping@2.11.21:
1684 + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==}
1685 + engines: {node: '>=6.0.0'}
1686 + hasBin: true
1687 +
1688 + boolbase@1.0.0:
1689 + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
1690 +
1691 + brace-expansion@1.1.18:
1692 + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
1693 +
1694 + brace-expansion@5.0.9:
1695 + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
1696 + engines: {node: 20 || >=22}
1697 +
1698 + braces@3.0.3:
1699 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
1700 + engines: {node: '>=8'}
1701 +
1702 + browserslist@4.28.9:
1703 + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==}
1704 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
1705 + hasBin: true
1706 +
1707 + buffer-from@1.1.2:
1708 + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
1709 +
1710 + cac@6.7.14:
1711 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
1712 + engines: {node: '>=8'}
1713 +
1714 + call-bind-apply-helpers@1.0.2:
1715 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
1716 + engines: {node: '>= 0.4'}
1717 +
1718 + call-bind@1.0.9:
1719 + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
1720 + engines: {node: '>= 0.4'}
1721 +
1722 + call-bound@1.0.4:
1723 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
1724 + engines: {node: '>= 0.4'}
1725 +
1726 + callsites@3.1.0:
1727 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
1728 + engines: {node: '>=6'}
1729 +
1730 + caniuse-lite@1.0.30001810:
1731 + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
1732 +
1733 + chai@5.3.3:
1734 + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
1735 + engines: {node: '>=18'}
1736 +
1737 + chalk@4.1.2:
1738 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
1739 + engines: {node: '>=10'}
1740 +
1741 + check-error@2.1.3:
1742 + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
1743 + engines: {node: '>= 16'}
1744 +
1745 + cheerio-select@2.1.0:
1746 + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
1747 +
1748 + cheerio@1.2.0:
1749 + resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==}
1750 + engines: {node: '>=20.18.1'}
1751 +
1752 + client-only@0.0.1:
1753 + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
1754 +
1755 + color-convert@2.0.1:
1756 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
1757 + engines: {node: '>=7.0.0'}
1758 +
1759 + color-name@1.1.4:
1760 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
1761 +
1762 + concat-map@0.0.1:
1763 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
1764 +
1765 + convert-source-map@2.0.0:
1766 + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
1767 +
1768 + cross-spawn@7.0.6:
1769 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
1770 + engines: {node: '>= 8'}
1771 +
1772 + css-select@5.2.2:
1773 + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
1774 +
1775 + css-what@6.2.2:
1776 + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
1777 + engines: {node: '>= 6'}
1778 +
1779 + csstype@3.2.3:
1780 + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
1781 +
1782 + damerau-levenshtein@1.0.8:
1783 + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
1784 +
1785 + data-view-buffer@1.0.2:
1786 + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
1787 + engines: {node: '>= 0.4'}
1788 +
1789 + data-view-byte-length@1.0.2:
1790 + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
1791 + engines: {node: '>= 0.4'}
1792 +
1793 + data-view-byte-offset@1.0.1:
1794 + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
1795 + engines: {node: '>= 0.4'}
1796 +
1797 + debug@3.2.7:
1798 + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
1799 + peerDependencies:
1800 + supports-color: '*'
1801 + peerDependenciesMeta:
1802 + supports-color:
1803 + optional: true
1804 +
1805 + debug@4.4.3:
1806 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
1807 + engines: {node: '>=6.0'}
1808 + peerDependencies:
1809 + supports-color: '*'
1810 + peerDependenciesMeta:
1811 + supports-color:
1812 + optional: true
1813 +
1814 + deep-eql@5.0.2:
1815 + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
1816 + engines: {node: '>=6'}
1817 +
1818 + deep-is@0.1.4:
1819 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
1820 +
1821 + define-data-property@1.1.4:
1822 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
1823 + engines: {node: '>= 0.4'}
1824 +
1825 + define-properties@1.2.1:
1826 + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
1827 + engines: {node: '>= 0.4'}
1828 +
1829 + detect-libc@2.1.2:
1830 + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
1831 + engines: {node: '>=8'}
1832 +
1833 + doctrine@2.1.0:
1834 + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
1835 + engines: {node: '>=0.10.0'}
1836 +
1837 + dom-serializer@2.0.0:
1838 + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
1839 +
1840 + domelementtype@2.3.0:
1841 + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
1842 +
1843 + domhandler@5.0.3:
1844 + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
1845 + engines: {node: '>= 4'}
1846 +
1847 + domutils@3.2.2:
1848 + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
1849 +
1850 + drizzle-kit@0.31.10:
1851 + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
1852 + hasBin: true
1853 +
1854 + drizzle-orm@0.45.2:
1855 + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==}
1856 + peerDependencies:
1857 + '@aws-sdk/client-rds-data': '>=3'
1858 + '@cloudflare/workers-types': '>=4'
1859 + '@electric-sql/pglite': '>=0.2.0'
1860 + '@libsql/client': '>=0.10.0'
1861 + '@libsql/client-wasm': '>=0.10.0'
1862 + '@neondatabase/serverless': '>=0.10.0'
1863 + '@op-engineering/op-sqlite': '>=2'
1864 + '@opentelemetry/api': ^1.4.1
1865 + '@planetscale/database': '>=1.13'
1866 + '@prisma/client': '*'
1867 + '@tidbcloud/serverless': '*'
1868 + '@types/better-sqlite3': '*'
1869 + '@types/pg': '*'
1870 + '@types/sql.js': '*'
1871 + '@upstash/redis': '>=1.34.7'
1872 + '@vercel/postgres': '>=0.8.0'
1873 + '@xata.io/client': '*'
1874 + better-sqlite3: '>=7'
1875 + bun-types: '*'
1876 + expo-sqlite: '>=14.0.0'
1877 + gel: '>=2'
1878 + knex: '*'
1879 + kysely: '*'
1880 + mysql2: '>=2'
1881 + pg: '>=8'
1882 + postgres: '>=3'
1883 + prisma: '*'
1884 + sql.js: '>=1'
1885 + sqlite3: '>=5'
1886 + peerDependenciesMeta:
1887 + '@aws-sdk/client-rds-data':
1888 + optional: true
1889 + '@cloudflare/workers-types':
1890 + optional: true
1891 + '@electric-sql/pglite':
1892 + optional: true
1893 + '@libsql/client':
1894 + optional: true
1895 + '@libsql/client-wasm':
1896 + optional: true
1897 + '@neondatabase/serverless':
1898 + optional: true
1899 + '@op-engineering/op-sqlite':
1900 + optional: true
1901 + '@opentelemetry/api':
1902 + optional: true
1903 + '@planetscale/database':
1904 + optional: true
1905 + '@prisma/client':
1906 + optional: true
1907 + '@tidbcloud/serverless':
1908 + optional: true
1909 + '@types/better-sqlite3':
1910 + optional: true
1911 + '@types/pg':
1912 + optional: true
1913 + '@types/sql.js':
1914 + optional: true
1915 + '@upstash/redis':
1916 + optional: true
1917 + '@vercel/postgres':
1918 + optional: true
1919 + '@xata.io/client':
1920 + optional: true
1921 + better-sqlite3:
1922 + optional: true
1923 + bun-types:
1924 + optional: true
1925 + expo-sqlite:
1926 + optional: true
1927 + gel:
1928 + optional: true
1929 + knex:
1930 + optional: true
1931 + kysely:
1932 + optional: true
1933 + mysql2:
1934 + optional: true
1935 + pg:
1936 + optional: true
1937 + postgres:
1938 + optional: true
1939 + prisma:
1940 + optional: true
1941 + sql.js:
1942 + optional: true
1943 + sqlite3:
1944 + optional: true
1945 +
1946 + dunder-proto@1.0.1:
1947 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
1948 + engines: {node: '>= 0.4'}
1949 +
1950 + electron-to-chromium@1.5.422:
1951 + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==}
1952 +
1953 + emoji-regex@9.2.2:
1954 + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
1955 +
1956 + encoding-sniffer@0.2.1:
1957 + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
1958 +
1959 + enhanced-resolve@5.24.5:
1960 + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==}
1961 + engines: {node: '>=10.13.0'}
1962 +
1963 + entities@4.5.0:
1964 + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
1965 + engines: {node: '>=0.12'}
1966 +
1967 + entities@6.0.1:
1968 + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
1969 + engines: {node: '>=0.12'}
1970 +
1971 + entities@7.0.1:
1972 + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
1973 + engines: {node: '>=0.12'}
1974 +
1975 + es-abstract-get@1.0.0:
1976 + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
1977 + engines: {node: '>= 0.4'}
1978 +
1979 + es-abstract@1.24.2:
1980 + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
1981 + engines: {node: '>= 0.4'}
1982 +
1983 + es-define-property@1.0.1:
1984 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
1985 + engines: {node: '>= 0.4'}
1986 +
1987 + es-errors@1.3.0:
1988 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
1989 + engines: {node: '>= 0.4'}
1990 +
1991 + es-iterator-helpers@1.4.0:
1992 + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==}
1993 + engines: {node: '>= 0.4'}
1994 +
1995 + es-module-lexer@1.7.0:
1996 + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
1997 +
1998 + es-object-atoms@1.1.2:
1999 + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
2000 + engines: {node: '>= 0.4'}
2001 +
2002 + es-set-tostringtag@2.1.0:
2003 + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
2004 + engines: {node: '>= 0.4'}
2005 +
2006 + es-shim-unscopables@1.1.0:
2007 + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
2008 + engines: {node: '>= 0.4'}
2009 +
2010 + es-to-primitive@1.3.4:
2011 + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==}
2012 + engines: {node: '>= 0.4'}
2013 +
2014 + esbuild@0.18.20:
2015 + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
2016 + engines: {node: '>=12'}
2017 + hasBin: true
2018 +
2019 + esbuild@0.25.12:
2020 + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
2021 + engines: {node: '>=18'}
2022 + hasBin: true
2023 +
2024 + esbuild@0.28.2:
2025 + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
2026 + engines: {node: '>=18'}
2027 + hasBin: true
2028 +
2029 + escalade@3.2.0:
2030 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
2031 + engines: {node: '>=6'}
2032 +
2033 + escape-string-regexp@4.0.0:
2034 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
2035 + engines: {node: '>=10'}
2036 +
2037 + eslint-config-next@16.3.4:
2038 + resolution: {integrity: sha512-35/8RM10huEL9vlr8hUZMERMENHBrnyHN3ZZkF9efSgzGaqK34jIqry44A956//zriUhUAUW0XSkcolhrryqAA==}
2039 + peerDependencies:
2040 + eslint: '>=9.0.0'
2041 + typescript: '>=3.3.1'
2042 + peerDependenciesMeta:
2043 + typescript:
2044 + optional: true
2045 +
2046 + eslint-import-resolver-node@0.3.10:
2047 + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==}
2048 +
2049 + eslint-import-resolver-typescript@3.10.1:
2050 + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
2051 + engines: {node: ^14.18.0 || >=16.0.0}
2052 + peerDependencies:
2053 + eslint: '*'
2054 + eslint-plugin-import: '*'
2055 + eslint-plugin-import-x: '*'
2056 + peerDependenciesMeta:
2057 + eslint-plugin-import:
2058 + optional: true
2059 + eslint-plugin-import-x:
2060 + optional: true
2061 +
2062 + eslint-module-utils@2.14.0:
2063 + resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==}
2064 + engines: {node: '>=4'}
2065 + peerDependencies:
2066 + '@typescript-eslint/parser': '*'
2067 + eslint: '*'
2068 + eslint-import-resolver-node: '*'
2069 + eslint-import-resolver-typescript: '*'
2070 + eslint-import-resolver-webpack: '*'
2071 + peerDependenciesMeta:
2072 + '@typescript-eslint/parser':
2073 + optional: true
2074 + eslint:
2075 + optional: true
2076 + eslint-import-resolver-node:
2077 + optional: true
2078 + eslint-import-resolver-typescript:
2079 + optional: true
2080 + eslint-import-resolver-webpack:
2081 + optional: true
2082 +
2083 + eslint-plugin-import@2.32.0:
2084 + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
2085 + engines: {node: '>=4'}
2086 + peerDependencies:
2087 + '@typescript-eslint/parser': '*'
2088 + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
2089 + peerDependenciesMeta:
2090 + '@typescript-eslint/parser':
2091 + optional: true
2092 +
2093 + eslint-plugin-jsx-a11y@6.10.2:
2094 + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
2095 + engines: {node: '>=4.0'}
2096 + peerDependencies:
2097 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
2098 +
2099 + eslint-plugin-react-hooks@7.1.1:
2100 + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==}
2101 + engines: {node: '>=18'}
2102 + peerDependencies:
2103 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0
2104 +
2105 + eslint-plugin-react@7.37.5:
2106 + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
2107 + engines: {node: '>=4'}
2108 + peerDependencies:
2109 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
2110 +
2111 + eslint-scope@8.4.0:
2112 + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
2113 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2114 +
2115 + eslint-visitor-keys@3.4.3:
2116 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
2117 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
2118 +
2119 + eslint-visitor-keys@4.2.1:
2120 + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
2121 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2122 +
2123 + eslint-visitor-keys@5.0.1:
2124 + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
2125 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
2126 +
2127 + eslint@9.39.5:
2128 + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
2129 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2130 + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
2131 + hasBin: true
2132 + peerDependencies:
2133 + jiti: '*'
2134 + peerDependenciesMeta:
2135 + jiti:
2136 + optional: true
2137 +
2138 + espree@10.4.0:
2139 + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
2140 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2141 +
2142 + esquery@1.7.0:
2143 + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
2144 + engines: {node: '>=0.10'}
2145 +
2146 + esrecurse@4.3.0:
2147 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
2148 + engines: {node: '>=4.0'}
2149 +
2150 + estraverse@5.3.0:
2151 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
2152 + engines: {node: '>=4.0'}
2153 +
2154 + estree-walker@3.0.3:
2155 + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
2156 +
2157 + esutils@2.0.3:
2158 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
2159 + engines: {node: '>=0.10.0'}
2160 +
2161 + expect-type@1.4.0:
2162 + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
2163 + engines: {node: '>=12.0.0'}
2164 +
2165 + fast-deep-equal@3.1.3:
2166 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
2167 +
2168 + fast-glob@3.3.1:
2169 + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
2170 + engines: {node: '>=8.6.0'}
2171 +
2172 + fast-json-stable-stringify@2.1.0:
2173 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
2174 +
2175 + fast-levenshtein@2.0.6:
2176 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
2177 +
2178 + fastq@1.20.3:
2179 + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==}
2180 +
2181 + fdir@6.5.0:
2182 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
2183 + engines: {node: '>=12.0.0'}
2184 + peerDependencies:
2185 + picomatch: ^3 || ^4
2186 + peerDependenciesMeta:
2187 + picomatch:
2188 + optional: true
2189 +
2190 + file-entry-cache@8.0.0:
2191 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
2192 + engines: {node: '>=16.0.0'}
2193 +
2194 + fill-range@7.1.1:
2195 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
2196 + engines: {node: '>=8'}
2197 +
2198 + find-up@5.0.0:
2199 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
2200 + engines: {node: '>=10'}
2201 +
2202 + flat-cache@4.0.1:
2203 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
2204 + engines: {node: '>=16'}
2205 +
2206 + flatted@3.4.4:
2207 + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
2208 +
2209 + for-each@0.3.5:
2210 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
2211 + engines: {node: '>= 0.4'}
2212 +
2213 + fsevents@2.3.3:
2214 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
2215 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
2216 + os: [darwin]
2217 +
2218 + function-bind@1.1.2:
2219 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
2220 +
2221 + function.prototype.name@1.2.0:
2222 + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==}
2223 + engines: {node: '>= 0.4'}
2224 +
2225 + functions-have-names@1.2.3:
2226 + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
2227 +
2228 + generator-function@2.0.1:
2229 + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
2230 + engines: {node: '>= 0.4'}
2231 +
2232 + gensync@1.0.0-beta.2:
2233 + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
2234 + engines: {node: '>=6.9.0'}
2235 +
2236 + get-intrinsic@1.3.0:
2237 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
2238 + engines: {node: '>= 0.4'}
2239 +
2240 + get-proto@1.0.1:
2241 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
2242 + engines: {node: '>= 0.4'}
2243 +
2244 + get-symbol-description@1.1.0:
2245 + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
2246 + engines: {node: '>= 0.4'}
2247 +
2248 + get-tsconfig@4.14.3:
2249 + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==}
2250 +
2251 + glob-parent@5.1.2:
2252 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
2253 + engines: {node: '>= 6'}
2254 +
2255 + glob-parent@6.0.2:
2256 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
2257 + engines: {node: '>=10.13.0'}
2258 +
2259 + globals@14.0.0:
2260 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
2261 + engines: {node: '>=18'}
2262 +
2263 + globals@16.4.0:
2264 + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
2265 + engines: {node: '>=18'}
2266 +
2267 + globalthis@1.0.4:
2268 + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
2269 + engines: {node: '>= 0.4'}
2270 +
2271 + gopd@1.2.0:
2272 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
2273 + engines: {node: '>= 0.4'}
2274 +
2275 + graceful-fs@4.2.11:
2276 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
2277 +
2278 + has-bigints@1.1.0:
2279 + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
2280 + engines: {node: '>= 0.4'}
2281 +
2282 + has-flag@4.0.0:
2283 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
2284 + engines: {node: '>=8'}
2285 +
2286 + has-property-descriptors@1.0.2:
2287 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
2288 +
2289 + has-proto@1.2.0:
2290 + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
2291 + engines: {node: '>= 0.4'}
2292 +
2293 + has-symbols@1.1.0:
2294 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
2295 + engines: {node: '>= 0.4'}
2296 +
2297 + has-tostringtag@1.0.2:
2298 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
2299 + engines: {node: '>= 0.4'}
2300 +
2301 + hasown@2.0.4:
2302 + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
2303 + engines: {node: '>= 0.4'}
2304 +
2305 + hermes-estree@0.25.1:
2306 + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
2307 +
2308 + hermes-parser@0.25.1:
2309 + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
2310 +
2311 + htmlparser2@10.1.0:
2312 + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
2313 +
2314 + iconv-lite@0.6.3:
2315 + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
2316 + engines: {node: '>=0.10.0'}
2317 +
2318 + ignore@5.3.2:
2319 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
2320 + engines: {node: '>= 4'}
2321 +
2322 + ignore@7.0.8:
2323 + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==}
2324 + engines: {node: '>= 4'}
2325 +
2326 + import-fresh@3.3.1:
2327 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
2328 + engines: {node: '>=6'}
2329 +
2330 + imurmurhash@0.1.4:
2331 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
2332 + engines: {node: '>=0.8.19'}
2333 +
2334 + internal-slot@1.1.0:
2335 + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
2336 + engines: {node: '>= 0.4'}
2337 +
2338 + is-array-buffer@3.0.5:
2339 + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
2340 + engines: {node: '>= 0.4'}
2341 +
2342 + is-async-function@2.1.1:
2343 + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
2344 + engines: {node: '>= 0.4'}
2345 +
2346 + is-bigint@1.1.0:
2347 + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
2348 + engines: {node: '>= 0.4'}
2349 +
2350 + is-boolean-object@1.2.2:
2351 + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
2352 + engines: {node: '>= 0.4'}
2353 +
2354 + is-bun-module@2.0.0:
2355 + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
2356 +
2357 + is-callable@1.2.7:
2358 + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
2359 + engines: {node: '>= 0.4'}
2360 +
2361 + is-core-module@2.16.2:
2362 + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
2363 + engines: {node: '>= 0.4'}
2364 +
2365 + is-data-view@1.0.2:
2366 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
2367 + engines: {node: '>= 0.4'}
2368 +
2369 + is-date-object@1.1.0:
2370 + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
2371 + engines: {node: '>= 0.4'}
2372 +
2373 + is-document.all@1.0.0:
2374 + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
2375 + engines: {node: '>= 0.4'}
2376 +
2377 + is-extglob@2.1.1:
2378 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
2379 + engines: {node: '>=0.10.0'}
2380 +
2381 + is-finalizationregistry@1.1.1:
2382 + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
2383 + engines: {node: '>= 0.4'}
2384 +
2385 + is-generator-function@1.1.2:
2386 + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
2387 + engines: {node: '>= 0.4'}
2388 +
2389 + is-glob@4.0.3:
2390 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
2391 + engines: {node: '>=0.10.0'}
2392 +
2393 + is-map@2.0.3:
2394 + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
2395 + engines: {node: '>= 0.4'}
2396 +
2397 + is-negative-zero@2.0.3:
2398 + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
2399 + engines: {node: '>= 0.4'}
2400 +
2401 + is-number-object@1.1.1:
2402 + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
2403 + engines: {node: '>= 0.4'}
2404 +
2405 + is-number@7.0.0:
2406 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
2407 + engines: {node: '>=0.12.0'}
2408 +
2409 + is-regex@1.2.1:
2410 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
2411 + engines: {node: '>= 0.4'}
2412 +
2413 + is-set@2.0.3:
2414 + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
2415 + engines: {node: '>= 0.4'}
2416 +
2417 + is-shared-array-buffer@1.0.4:
2418 + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
2419 + engines: {node: '>= 0.4'}
2420 +
2421 + is-string@1.1.1:
2422 + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
2423 + engines: {node: '>= 0.4'}
2424 +
2425 + is-symbol@1.1.1:
2426 + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
2427 + engines: {node: '>= 0.4'}
2428 +
2429 + is-typed-array@1.1.15:
2430 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
2431 + engines: {node: '>= 0.4'}
2432 +
2433 + is-weakmap@2.0.2:
2434 + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
2435 + engines: {node: '>= 0.4'}
2436 +
2437 + is-weakref@1.1.1:
2438 + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
2439 + engines: {node: '>= 0.4'}
2440 +
2441 + is-weakset@2.0.4:
2442 + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
2443 + engines: {node: '>= 0.4'}
2444 +
2445 + isarray@2.0.5:
2446 + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
2447 +
2448 + isexe@2.0.0:
2449 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
2450 +
2451 + iterator.prototype@1.1.5:
2452 + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
2453 + engines: {node: '>= 0.4'}
2454 +
2455 + jiti@2.7.0:
2456 + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
2457 + hasBin: true
2458 +
2459 + js-tokens@4.0.0:
2460 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
2461 +
2462 + js-tokens@9.0.1:
2463 + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
2464 +
2465 + js-yaml@4.3.2:
2466 + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
2467 + hasBin: true
2468 +
2469 + jsesc@3.1.0:
2470 + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
2471 + engines: {node: '>=6'}
2472 + hasBin: true
2473 +
2474 + json-buffer@3.0.1:
2475 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
2476 +
2477 + json-schema-traverse@0.4.1:
2478 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
2479 +
2480 + json-stable-stringify-without-jsonify@1.0.1:
2481 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
2482 +
2483 + json5@1.0.2:
2484 + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
2485 + hasBin: true
2486 +
2487 + json5@2.2.3:
2488 + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
2489 + engines: {node: '>=6'}
2490 + hasBin: true
2491 +
2492 + jsx-ast-utils@3.3.5:
2493 + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
2494 + engines: {node: '>=4.0'}
2495 +
2496 + keyv@4.5.4:
2497 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
2498 +
2499 + language-subtag-registry@0.3.23:
2500 + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
2501 +
2502 + language-tags@1.0.9:
2503 + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
2504 + engines: {node: '>=0.10'}
2505 +
2506 + levn@0.4.1:
2507 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
2508 + engines: {node: '>= 0.8.0'}
2509 +
2510 + lightningcss-android-arm64@1.32.0:
2511 + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
2512 + engines: {node: '>= 12.0.0'}
2513 + cpu: [arm64]
2514 + os: [android]
2515 +
2516 + lightningcss-darwin-arm64@1.32.0:
2517 + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
2518 + engines: {node: '>= 12.0.0'}
2519 + cpu: [arm64]
2520 + os: [darwin]
2521 +
2522 + lightningcss-darwin-x64@1.32.0:
2523 + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
2524 + engines: {node: '>= 12.0.0'}
2525 + cpu: [x64]
2526 + os: [darwin]
2527 +
2528 + lightningcss-freebsd-x64@1.32.0:
2529 + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
2530 + engines: {node: '>= 12.0.0'}
2531 + cpu: [x64]
2532 + os: [freebsd]
2533 +
2534 + lightningcss-linux-arm-gnueabihf@1.32.0:
2535 + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
2536 + engines: {node: '>= 12.0.0'}
2537 + cpu: [arm]
2538 + os: [linux]
2539 +
2540 + lightningcss-linux-arm64-gnu@1.32.0:
2541 + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
2542 + engines: {node: '>= 12.0.0'}
2543 + cpu: [arm64]
2544 + os: [linux]
2545 + libc: [glibc]
2546 +
2547 + lightningcss-linux-arm64-musl@1.32.0:
2548 + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
2549 + engines: {node: '>= 12.0.0'}
2550 + cpu: [arm64]
2551 + os: [linux]
2552 + libc: [musl]
2553 +
2554 + lightningcss-linux-x64-gnu@1.32.0:
2555 + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
2556 + engines: {node: '>= 12.0.0'}
2557 + cpu: [x64]
2558 + os: [linux]
2559 + libc: [glibc]
2560 +
2561 + lightningcss-linux-x64-musl@1.32.0:
2562 + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
2563 + engines: {node: '>= 12.0.0'}
2564 + cpu: [x64]
2565 + os: [linux]
2566 + libc: [musl]
2567 +
2568 + lightningcss-win32-arm64-msvc@1.32.0:
2569 + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
2570 + engines: {node: '>= 12.0.0'}
2571 + cpu: [arm64]
2572 + os: [win32]
2573 +
2574 + lightningcss-win32-x64-msvc@1.32.0:
2575 + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
2576 + engines: {node: '>= 12.0.0'}
2577 + cpu: [x64]
2578 + os: [win32]
2579 +
2580 + lightningcss@1.32.0:
2581 + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
2582 + engines: {node: '>= 12.0.0'}
2583 +
2584 + locate-path@6.0.0:
2585 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
2586 + engines: {node: '>=10'}
2587 +
2588 + lodash.merge@4.6.2:
2589 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
2590 +
2591 + loose-envify@1.4.0:
2592 + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
2593 + hasBin: true
2594 +
2595 + loupe@3.2.1:
2596 + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
2597 +
2598 + lru-cache@5.1.1:
2599 + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
2600 +
2601 + lucide-react@1.41.0:
2602 + resolution: {integrity: sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==}
2603 + peerDependencies:
2604 + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
2605 +
2606 + magic-string@0.30.21:
2607 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
2608 +
2609 + math-intrinsics@1.1.0:
2610 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
2611 + engines: {node: '>= 0.4'}
2612 +
2613 + merge2@1.4.1:
2614 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
2615 + engines: {node: '>= 8'}
2616 +
2617 + micromatch@4.0.8:
2618 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
2619 + engines: {node: '>=8.6'}
2620 +
2621 + minimatch@10.2.6:
2622 + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
2623 + engines: {node: 18 || 20 || >=22}
2624 +
2625 + minimatch@3.1.5:
2626 + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
2627 +
2628 + minimist@1.2.8:
2629 + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
2630 +
2631 + ms@2.1.3:
2632 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
2633 +
2634 + nanoid@3.3.18:
2635 + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
2636 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
2637 + hasBin: true
2638 +
2639 + napi-postinstall@0.3.4:
2640 + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
2641 + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
2642 + hasBin: true
2643 +
2644 + natural-compare@1.4.0:
2645 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
2646 +
2647 + next@16.3.4:
2648 + resolution: {integrity: sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA==}
2649 + engines: {node: '>=20.9.0'}
2650 + hasBin: true
2651 + peerDependencies:
2652 + '@opentelemetry/api': ^1.1.0
2653 + '@playwright/test': ^1.51.1
2654 + babel-plugin-react-compiler: '*'
2655 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
2656 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
2657 + sass: ^1.3.0
2658 + peerDependenciesMeta:
2659 + '@opentelemetry/api':
2660 + optional: true
2661 + '@playwright/test':
2662 + optional: true
2663 + babel-plugin-react-compiler:
2664 + optional: true
2665 + sass:
2666 + optional: true
2667 +
2668 + node-exports-info@1.6.2:
2669 + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
2670 + engines: {node: '>= 0.4'}
2671 +
2672 + node-releases@2.0.54:
2673 + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==}
2674 + engines: {node: '>=18'}
2675 +
2676 + nth-check@2.1.1:
2677 + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
2678 +
2679 + object-assign@4.1.1:
2680 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
2681 + engines: {node: '>=0.10.0'}
2682 +
2683 + object-inspect@1.13.4:
2684 + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
2685 + engines: {node: '>= 0.4'}
2686 +
2687 + object-keys@1.1.1:
2688 + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
2689 + engines: {node: '>= 0.4'}
2690 +
2691 + object.assign@4.1.7:
2692 + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
2693 + engines: {node: '>= 0.4'}
2694 +
2695 + object.entries@1.1.9:
2696 + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
2697 + engines: {node: '>= 0.4'}
2698 +
2699 + object.fromentries@2.0.8:
2700 + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
2701 + engines: {node: '>= 0.4'}
2702 +
2703 + object.groupby@1.0.3:
2704 + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
2705 + engines: {node: '>= 0.4'}
2706 +
2707 + object.values@1.2.1:
2708 + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
2709 + engines: {node: '>= 0.4'}
2710 +
2711 + on-exit-leak-free@2.1.2:
2712 + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
2713 + engines: {node: '>=14.0.0'}
2714 +
2715 + optionator@0.9.4:
2716 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
2717 + engines: {node: '>= 0.8.0'}
2718 +
2719 + own-keys@1.0.2:
2720 + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==}
2721 + engines: {node: '>= 0.4'}
2722 +
2723 + p-limit@3.1.0:
2724 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
2725 + engines: {node: '>=10'}
2726 +
2727 + p-locate@5.0.0:
2728 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
2729 + engines: {node: '>=10'}
2730 +
2731 + parent-module@1.0.1:
2732 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
2733 + engines: {node: '>=6'}
2734 +
2735 + parse5-htmlparser2-tree-adapter@7.1.0:
2736 + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
2737 +
2738 + parse5-parser-stream@7.1.2:
2739 + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
2740 +
2741 + parse5@7.3.0:
2742 + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
2743 +
2744 + path-exists@4.0.0:
2745 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
2746 + engines: {node: '>=8'}
2747 +
2748 + path-key@3.1.1:
2749 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
2750 + engines: {node: '>=8'}
2751 +
2752 + path-parse@1.0.7:
2753 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
2754 +
2755 + pathe@2.0.3:
2756 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
2757 +
2758 + pathval@2.0.1:
2759 + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
2760 + engines: {node: '>= 14.16'}
2761 +
2762 + picocolors@1.1.1:
2763 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
2764 +
2765 + picomatch@2.3.2:
2766 + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
2767 + engines: {node: '>=8.6'}
2768 +
2769 + picomatch@4.0.7:
2770 + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
2771 + engines: {node: '>=12'}
2772 +
2773 + pino-abstract-transport@2.0.0:
2774 + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
2775 +
2776 + pino-std-serializers@7.1.0:
2777 + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
2778 +
2779 + pino@9.14.0:
2780 + resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==}
2781 + hasBin: true
2782 +
2783 + possible-typed-array-names@1.1.0:
2784 + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
2785 + engines: {node: '>= 0.4'}
2786 +
2787 + postcss@8.5.23:
2788 + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
2789 + engines: {node: ^10 || ^12 || >=14}
2790 +
2791 + postcss@8.5.28:
2792 + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
2793 + engines: {node: ^10 || ^12 || >=14}
2794 +
2795 + postgres@3.4.9:
2796 + resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==}
2797 + engines: {node: '>=12'}
2798 +
2799 + prelude-ls@1.2.1:
2800 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
2801 + engines: {node: '>= 0.8.0'}
2802 +
2803 + process-warning@5.1.0:
2804 + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==}
2805 +
2806 + prop-types@15.8.1:
2807 + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
2808 +
2809 + punycode@2.3.1:
2810 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
2811 + engines: {node: '>=6'}
2812 +
2813 + queue-microtask@1.2.3:
2814 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
2815 +
2816 + quick-format-unescaped@4.0.4:
2817 + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
2818 +
2819 + react-dom@19.2.8:
2820 + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==}
2821 + peerDependencies:
2822 + react: ^19.2.8
2823 +
2824 + react-is@16.13.1:
2825 + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
2826 +
2827 + react@19.2.8:
2828 + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
2829 + engines: {node: '>=0.10.0'}
2830 +
2831 + real-require@0.2.0:
2832 + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
2833 + engines: {node: '>= 12.13.0'}
2834 +
2835 + reflect.getprototypeof@1.0.10:
2836 + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
2837 + engines: {node: '>= 0.4'}
2838 +
2839 + regexp.prototype.flags@1.5.4:
2840 + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
2841 + engines: {node: '>= 0.4'}
2842 +
2843 + resolve-from@4.0.0:
2844 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
2845 + engines: {node: '>=4'}
2846 +
2847 + resolve-pkg-maps@1.0.0:
2848 + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
2849 +
2850 + resolve@2.0.0-next.7:
2851 + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==}
2852 + engines: {node: '>= 0.4'}
2853 + hasBin: true
2854 +
2855 + reusify@1.1.0:
2856 + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
2857 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
2858 +
2859 + rollup@4.63.1:
2860 + resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==}
2861 + engines: {node: '>=18.0.0', npm: '>=8.0.0'}
2862 + hasBin: true
2863 +
2864 + run-parallel@1.2.0:
2865 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
2866 +
2867 + safe-array-concat@1.1.4:
2868 + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
2869 + engines: {node: '>=0.4'}
2870 +
2871 + safe-push-apply@1.0.0:
2872 + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
2873 + engines: {node: '>= 0.4'}
2874 +
2875 + safe-regex-test@1.1.0:
2876 + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
2877 + engines: {node: '>= 0.4'}
2878 +
2879 + safe-stable-stringify@2.5.0:
2880 + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
2881 + engines: {node: '>=10'}
2882 +
2883 + safer-buffer@2.1.2:
2884 + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
2885 +
2886 + scheduler@0.27.0:
2887 + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
2888 +
2889 + semver@6.3.1:
2890 + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
2891 + hasBin: true
2892 +
2893 + semver@7.8.5:
2894 + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
2895 + engines: {node: '>=10'}
2896 + hasBin: true
2897 +
2898 + server-only@0.0.1:
2899 + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
2900 +
2901 + set-function-length@1.2.2:
2902 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
2903 + engines: {node: '>= 0.4'}
2904 +
2905 + set-function-name@2.0.2:
2906 + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
2907 + engines: {node: '>= 0.4'}
2908 +
2909 + set-proto@1.0.0:
2910 + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
2911 + engines: {node: '>= 0.4'}
2912 +
2913 + sharp@0.35.4:
2914 + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
2915 + engines: {node: '>=20.9.0'}
2916 + peerDependencies:
2917 + '@types/node': '*'
2918 + peerDependenciesMeta:
2919 + '@types/node':
2920 + optional: true
2921 +
2922 + shebang-command@2.0.0:
2923 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
2924 + engines: {node: '>=8'}
2925 +
2926 + shebang-regex@3.0.0:
2927 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
2928 + engines: {node: '>=8'}
2929 +
2930 + side-channel-list@1.0.1:
2931 + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
2932 + engines: {node: '>= 0.4'}
2933 +
2934 + side-channel-map@1.0.1:
2935 + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
2936 + engines: {node: '>= 0.4'}
2937 +
2938 + side-channel-weakmap@1.0.2:
2939 + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
2940 + engines: {node: '>= 0.4'}
2941 +
2942 + side-channel@1.1.1:
2943 + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
2944 + engines: {node: '>= 0.4'}
2945 +
2946 + siginfo@2.0.0:
2947 + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
2948 +
2949 + sonic-boom@4.2.1:
2950 + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
2951 +
2952 + source-map-js@1.2.1:
2953 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
2954 + engines: {node: '>=0.10.0'}
2955 +
2956 + source-map-support@0.5.21:
2957 + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
2958 +
2959 + source-map@0.6.1:
2960 + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
2961 + engines: {node: '>=0.10.0'}
2962 +
2963 + split2@4.2.0:
2964 + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
2965 + engines: {node: '>= 10.x'}
2966 +
2967 + stable-hash@0.0.5:
2968 + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
2969 +
2970 + stackback@0.0.2:
2971 + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
2972 +
2973 + std-env@3.10.0:
2974 + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
2975 +
2976 + stop-iteration-iterator@1.1.0:
2977 + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
2978 + engines: {node: '>= 0.4'}
2979 +
2980 + string.prototype.includes@2.0.1:
2981 + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
2982 + engines: {node: '>= 0.4'}
2983 +
2984 + string.prototype.matchall@4.1.0:
2985 + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==}
2986 + engines: {node: '>= 0.4'}
2987 +
2988 + string.prototype.repeat@1.0.0:
2989 + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
2990 +
2991 + string.prototype.trim@1.2.11:
2992 + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==}
2993 + engines: {node: '>= 0.4'}
2994 +
2995 + string.prototype.trimend@1.0.10:
2996 + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==}
2997 + engines: {node: '>= 0.4'}
2998 +
2999 + string.prototype.trimstart@1.0.8:
3000 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
3001 + engines: {node: '>= 0.4'}
3002 +
3003 + strip-bom@3.0.0:
3004 + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
3005 + engines: {node: '>=4'}
3006 +
3007 + strip-json-comments@3.1.1:
3008 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
3009 + engines: {node: '>=8'}
3010 +
3011 + strip-literal@3.1.0:
3012 + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
3013 +
3014 + styled-jsx@5.1.6:
3015 + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
3016 + engines: {node: '>= 12.0.0'}
3017 + peerDependencies:
3018 + '@babel/core': '*'
3019 + babel-plugin-macros: '*'
3020 + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
3021 + peerDependenciesMeta:
3022 + '@babel/core':
3023 + optional: true
3024 + babel-plugin-macros:
3025 + optional: true
3026 +
3027 + supports-color@7.2.0:
3028 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
3029 + engines: {node: '>=8'}
3030 +
3031 + supports-preserve-symlinks-flag@1.0.0:
3032 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
3033 + engines: {node: '>= 0.4'}
3034 +
3035 + tailwindcss@4.3.3:
3036 + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==}
3037 +
3038 + tapable@2.3.3:
3039 + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
3040 + engines: {node: '>=6'}
3041 +
3042 + thread-stream@3.2.0:
3043 + resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==}
3044 +
3045 + tinybench@2.9.0:
3046 + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
3047 +
3048 + tinyexec@0.3.2:
3049 + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
3050 +
3051 + tinyglobby@0.2.17:
3052 + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
3053 + engines: {node: '>=12.0.0'}
3054 +
3055 + tinypool@1.1.1:
3056 + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
3057 + engines: {node: ^18.0.0 || >=20.0.0}
3058 +
3059 + tinyrainbow@2.0.0:
3060 + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
3061 + engines: {node: '>=14.0.0'}
3062 +
3063 + tinyspy@4.0.6:
3064 + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==}
3065 + engines: {node: '>=14.0.0'}
3066 +
3067 + to-regex-range@5.0.1:
3068 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
3069 + engines: {node: '>=8.0'}
3070 +
3071 + ts-api-utils@2.5.0:
3072 + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
3073 + engines: {node: '>=18.12'}
3074 + peerDependencies:
3075 + typescript: '>=4.8.4'
3076 +
3077 + tsconfig-paths@3.15.0:
3078 + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
3079 +
3080 + tslib@2.8.1:
3081 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
3082 +
3083 + tsx@4.23.13:
3084 + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==}
3085 + engines: {node: '>=18.0.0'}
3086 + hasBin: true
3087 +
3088 + type-check@0.4.0:
3089 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
3090 + engines: {node: '>= 0.8.0'}
3091 +
3092 + typed-array-buffer@1.0.3:
3093 + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
3094 + engines: {node: '>= 0.4'}
3095 +
3096 + typed-array-byte-length@1.0.3:
3097 + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
3098 + engines: {node: '>= 0.4'}
3099 +
3100 + typed-array-byte-offset@1.0.4:
3101 + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
3102 + engines: {node: '>= 0.4'}
3103 +
3104 + typed-array-length@1.0.8:
3105 + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
3106 + engines: {node: '>= 0.4'}
3107 +
3108 + typescript-eslint@8.69.0:
3109 + resolution: {integrity: sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==}
3110 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
3111 + peerDependencies:
3112 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
3113 + typescript: '>=4.8.4 <6.1.0'
3114 +
3115 + typescript@5.9.3:
3116 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
3117 + engines: {node: '>=14.17'}
3118 + hasBin: true
3119 +
3120 + unbox-primitive@1.1.0:
3121 + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
3122 + engines: {node: '>= 0.4'}
3123 +
3124 + undici-types@7.18.2:
3125 + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
3126 +
3127 + undici@7.29.1:
3128 + resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
3129 + engines: {node: '>=20.18.1'}
3130 +
3131 + unrs-resolver@1.12.2:
3132 + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==}
3133 +
3134 + update-browserslist-db@1.3.2:
3135 + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==}
3136 + hasBin: true
3137 + peerDependencies:
3138 + browserslist: '>= 4.21.0'
3139 +
3140 + uri-js@4.4.1:
3141 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
3142 +
3143 + vite-node@3.2.4:
3144 + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
3145 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
3146 + hasBin: true
3147 +
3148 + vite@7.3.6:
3149 + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==}
3150 + engines: {node: ^20.19.0 || >=22.12.0}
3151 + hasBin: true
3152 + peerDependencies:
3153 + '@types/node': ^20.19.0 || >=22.12.0
3154 + jiti: '>=1.21.0'
3155 + less: ^4.0.0
3156 + lightningcss: ^1.21.0
3157 + sass: ^1.70.0
3158 + sass-embedded: ^1.70.0
3159 + stylus: '>=0.54.8'
3160 + sugarss: ^5.0.0
3161 + terser: ^5.16.0
3162 + tsx: ^4.8.1
3163 + yaml: ^2.4.2
3164 + peerDependenciesMeta:
3165 + '@types/node':
3166 + optional: true
3167 + jiti:
3168 + optional: true
3169 + less:
3170 + optional: true
3171 + lightningcss:
3172 + optional: true
3173 + sass:
3174 + optional: true
3175 + sass-embedded:
3176 + optional: true
3177 + stylus:
3178 + optional: true
3179 + sugarss:
3180 + optional: true
3181 + terser:
3182 + optional: true
3183 + tsx:
3184 + optional: true
3185 + yaml:
3186 + optional: true
3187 +
3188 + vitest@3.2.7:
3189 + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==}
3190 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
3191 + hasBin: true
3192 + peerDependencies:
3193 + '@edge-runtime/vm': '*'
3194 + '@types/debug': ^4.1.12
3195 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
3196 + '@vitest/browser': 3.2.7
3197 + '@vitest/ui': 3.2.7
3198 + happy-dom: '*'
3199 + jsdom: '*'
3200 + peerDependenciesMeta:
3201 + '@edge-runtime/vm':
3202 + optional: true
3203 + '@types/debug':
3204 + optional: true
3205 + '@types/node':
3206 + optional: true
3207 + '@vitest/browser':
3208 + optional: true
3209 + '@vitest/ui':
3210 + optional: true
3211 + happy-dom:
3212 + optional: true
3213 + jsdom:
3214 + optional: true
3215 +
3216 + whatwg-encoding@3.1.1:
3217 + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
3218 + engines: {node: '>=18'}
3219 + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
3220 +
3221 + whatwg-mimetype@4.0.0:
3222 + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
3223 + engines: {node: '>=18'}
3224 +
3225 + which-boxed-primitive@1.1.1:
3226 + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
3227 + engines: {node: '>= 0.4'}
3228 +
3229 + which-builtin-type@1.2.1:
3230 + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
3231 + engines: {node: '>= 0.4'}
3232 +
3233 + which-collection@1.0.2:
3234 + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
3235 + engines: {node: '>= 0.4'}
3236 +
3237 + which-typed-array@1.1.22:
3238 + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
3239 + engines: {node: '>= 0.4'}
3240 +
3241 + which@2.0.2:
3242 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
3243 + engines: {node: '>= 8'}
3244 + hasBin: true
3245 +
3246 + why-is-node-running@2.3.0:
3247 + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
3248 + engines: {node: '>=8'}
3249 + hasBin: true
3250 +
3251 + word-wrap@1.2.5:
3252 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
3253 + engines: {node: '>=0.10.0'}
3254 +
3255 + yallist@3.1.1:
3256 + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
3257 +
3258 + yocto-queue@0.1.0:
3259 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
3260 + engines: {node: '>=10'}
3261 +
3262 + zod-validation-error@4.0.2:
3263 + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
3264 + engines: {node: '>=18.0.0'}
3265 + peerDependencies:
3266 + zod: ^3.25.0 || ^4.0.0
3267 +
3268 + zod@4.5.4:
3269 + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==}
3270 +
3271 +snapshots:
3272 +
3273 + '@alloc/quick-lru@5.3.0': {}
3274 +
3275 + '@babel/code-frame@7.29.7':
3276 + dependencies:
3277 + '@babel/helper-validator-identifier': 7.29.7
3278 + js-tokens: 4.0.0
3279 + picocolors: 1.1.1
3280 +
3281 + '@babel/compat-data@7.29.7': {}
3282 +
3283 + '@babel/core@7.29.7':
3284 + dependencies:
3285 + '@babel/code-frame': 7.29.7
3286 + '@babel/generator': 7.29.8
3287 + '@babel/helper-compilation-targets': 7.29.7
3288 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
3289 + '@babel/helpers': 7.29.7
3290 + '@babel/parser': 7.29.8
3291 + '@babel/template': 7.29.7
3292 + '@babel/traverse': 7.29.8
3293 + '@babel/types': 7.29.8
3294 + '@jridgewell/remapping': 2.3.5
3295 + convert-source-map: 2.0.0
3296 + debug: 4.4.3
3297 + gensync: 1.0.0-beta.2
3298 + json5: 2.2.3
3299 + semver: 6.3.1
3300 + transitivePeerDependencies:
3301 + - supports-color
3302 +
3303 + '@babel/generator@7.29.8':
3304 + dependencies:
3305 + '@babel/parser': 7.29.8
3306 + '@babel/types': 7.29.8
3307 + '@jridgewell/gen-mapping': 0.3.13
3308 + '@jridgewell/trace-mapping': 0.3.31
3309 + jsesc: 3.1.0
3310 +
3311 + '@babel/helper-compilation-targets@7.29.7':
3312 + dependencies:
3313 + '@babel/compat-data': 7.29.7
3314 + '@babel/helper-validator-option': 7.29.7
3315 + browserslist: 4.28.9
3316 + lru-cache: 5.1.1
3317 + semver: 6.3.1
3318 +
3319 + '@babel/helper-globals@7.29.7': {}
3320 +
3321 + '@babel/helper-module-imports@7.29.7':
3322 + dependencies:
3323 + '@babel/traverse': 7.29.8
3324 + '@babel/types': 7.29.8
3325 + transitivePeerDependencies:
3326 + - supports-color
3327 +
3328 + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
3329 + dependencies:
3330 + '@babel/core': 7.29.7
3331 + '@babel/helper-module-imports': 7.29.7
3332 + '@babel/helper-validator-identifier': 7.29.7
3333 + '@babel/traverse': 7.29.8
3334 + transitivePeerDependencies:
3335 + - supports-color
3336 +
3337 + '@babel/helper-string-parser@7.29.7': {}
3338 +
3339 + '@babel/helper-validator-identifier@7.29.7': {}
3340 +
3341 + '@babel/helper-validator-option@7.29.7': {}
3342 +
3343 + '@babel/helpers@7.29.7':
3344 + dependencies:
3345 + '@babel/template': 7.29.7
3346 + '@babel/types': 7.29.8
3347 +
3348 + '@babel/parser@7.29.8':
3349 + dependencies:
3350 + '@babel/types': 7.29.8
3351 +
3352 + '@babel/template@7.29.7':
3353 + dependencies:
3354 + '@babel/code-frame': 7.29.7
3355 + '@babel/parser': 7.29.8
3356 + '@babel/types': 7.29.8
3357 +
3358 + '@babel/traverse@7.29.8':
3359 + dependencies:
3360 + '@babel/code-frame': 7.29.7
3361 + '@babel/generator': 7.29.8
3362 + '@babel/helper-globals': 7.29.7
3363 + '@babel/parser': 7.29.8
3364 + '@babel/template': 7.29.7
3365 + '@babel/types': 7.29.8
3366 + debug: 4.4.3
3367 + transitivePeerDependencies:
3368 + - supports-color
3369 +
3370 + '@babel/types@7.29.8':
3371 + dependencies:
3372 + '@babel/helper-string-parser': 7.29.7
3373 + '@babel/helper-validator-identifier': 7.29.7
3374 +
3375 + '@drizzle-team/brocli@0.10.2': {}
3376 +
3377 + '@emnapi/core@1.10.0':
3378 + dependencies:
3379 + '@emnapi/wasi-threads': 1.2.1
3380 + tslib: 2.8.1
3381 + optional: true
3382 +
3383 + '@emnapi/runtime@1.10.0':
3384 + dependencies:
3385 + tslib: 2.8.1
3386 + optional: true
3387 +
3388 + '@emnapi/runtime@1.11.3':
3389 + dependencies:
3390 + tslib: 2.8.1
3391 + optional: true
3392 +
3393 + '@emnapi/wasi-threads@1.2.1':
3394 + dependencies:
3395 + tslib: 2.8.1
3396 + optional: true
3397 +
3398 + '@esbuild-kit/core-utils@3.3.2':
3399 + dependencies:
3400 + esbuild: 0.18.20
3401 + source-map-support: 0.5.21
3402 +
3403 + '@esbuild-kit/esm-loader@2.6.5':
3404 + dependencies:
3405 + '@esbuild-kit/core-utils': 3.3.2
3406 + get-tsconfig: 4.14.3
3407 +
3408 + '@esbuild/aix-ppc64@0.25.12':
3409 + optional: true
3410 +
3411 + '@esbuild/aix-ppc64@0.28.2':
3412 + optional: true
3413 +
3414 + '@esbuild/android-arm64@0.18.20':
3415 + optional: true
3416 +
3417 + '@esbuild/android-arm64@0.25.12':
3418 + optional: true
3419 +
3420 + '@esbuild/android-arm64@0.28.2':
3421 + optional: true
3422 +
3423 + '@esbuild/android-arm@0.18.20':
3424 + optional: true
3425 +
3426 + '@esbuild/android-arm@0.25.12':
3427 + optional: true
3428 +
3429 + '@esbuild/android-arm@0.28.2':
3430 + optional: true
3431 +
3432 + '@esbuild/android-x64@0.18.20':
3433 + optional: true
3434 +
3435 + '@esbuild/android-x64@0.25.12':
3436 + optional: true
3437 +
3438 + '@esbuild/android-x64@0.28.2':
3439 + optional: true
3440 +
3441 + '@esbuild/darwin-arm64@0.18.20':
3442 + optional: true
3443 +
3444 + '@esbuild/darwin-arm64@0.25.12':
3445 + optional: true
3446 +
3447 + '@esbuild/darwin-arm64@0.28.2':
3448 + optional: true
3449 +
3450 + '@esbuild/darwin-x64@0.18.20':
3451 + optional: true
3452 +
3453 + '@esbuild/darwin-x64@0.25.12':
3454 + optional: true
3455 +
3456 + '@esbuild/darwin-x64@0.28.2':
3457 + optional: true
3458 +
3459 + '@esbuild/freebsd-arm64@0.18.20':
3460 + optional: true
3461 +
3462 + '@esbuild/freebsd-arm64@0.25.12':
3463 + optional: true
3464 +
3465 + '@esbuild/freebsd-arm64@0.28.2':
3466 + optional: true
3467 +
3468 + '@esbuild/freebsd-x64@0.18.20':
3469 + optional: true
3470 +
3471 + '@esbuild/freebsd-x64@0.25.12':
3472 + optional: true
3473 +
3474 + '@esbuild/freebsd-x64@0.28.2':
3475 + optional: true
3476 +
3477 + '@esbuild/linux-arm64@0.18.20':
3478 + optional: true
3479 +
3480 + '@esbuild/linux-arm64@0.25.12':
3481 + optional: true
3482 +
3483 + '@esbuild/linux-arm64@0.28.2':
3484 + optional: true
3485 +
3486 + '@esbuild/linux-arm@0.18.20':
3487 + optional: true
3488 +
3489 + '@esbuild/linux-arm@0.25.12':
3490 + optional: true
3491 +
3492 + '@esbuild/linux-arm@0.28.2':
3493 + optional: true
3494 +
3495 + '@esbuild/linux-ia32@0.18.20':
3496 + optional: true
3497 +
3498 + '@esbuild/linux-ia32@0.25.12':
3499 + optional: true
3500 +
3501 + '@esbuild/linux-ia32@0.28.2':
3502 + optional: true
3503 +
3504 + '@esbuild/linux-loong64@0.18.20':
3505 + optional: true
3506 +
3507 + '@esbuild/linux-loong64@0.25.12':
3508 + optional: true
3509 +
3510 + '@esbuild/linux-loong64@0.28.2':
3511 + optional: true
3512 +
3513 + '@esbuild/linux-mips64el@0.18.20':
3514 + optional: true
3515 +
3516 + '@esbuild/linux-mips64el@0.25.12':
3517 + optional: true
3518 +
3519 + '@esbuild/linux-mips64el@0.28.2':
3520 + optional: true
3521 +
3522 + '@esbuild/linux-ppc64@0.18.20':
3523 + optional: true
3524 +
3525 + '@esbuild/linux-ppc64@0.25.12':
3526 + optional: true
3527 +
3528 + '@esbuild/linux-ppc64@0.28.2':
3529 + optional: true
3530 +
3531 + '@esbuild/linux-riscv64@0.18.20':
3532 + optional: true
3533 +
3534 + '@esbuild/linux-riscv64@0.25.12':
3535 + optional: true
3536 +
3537 + '@esbuild/linux-riscv64@0.28.2':
3538 + optional: true
3539 +
3540 + '@esbuild/linux-s390x@0.18.20':
3541 + optional: true
3542 +
3543 + '@esbuild/linux-s390x@0.25.12':
3544 + optional: true
3545 +
3546 + '@esbuild/linux-s390x@0.28.2':
3547 + optional: true
3548 +
3549 + '@esbuild/linux-x64@0.18.20':
3550 + optional: true
3551 +
3552 + '@esbuild/linux-x64@0.25.12':
3553 + optional: true
3554 +
3555 + '@esbuild/linux-x64@0.28.2':
3556 + optional: true
3557 +
3558 + '@esbuild/netbsd-arm64@0.25.12':
3559 + optional: true
3560 +
3561 + '@esbuild/netbsd-arm64@0.28.2':
3562 + optional: true
3563 +
3564 + '@esbuild/netbsd-x64@0.18.20':
3565 + optional: true
3566 +
3567 + '@esbuild/netbsd-x64@0.25.12':
3568 + optional: true
3569 +
3570 + '@esbuild/netbsd-x64@0.28.2':
3571 + optional: true
3572 +
3573 + '@esbuild/openbsd-arm64@0.25.12':
3574 + optional: true
3575 +
3576 + '@esbuild/openbsd-arm64@0.28.2':
3577 + optional: true
3578 +
3579 + '@esbuild/openbsd-x64@0.18.20':
3580 + optional: true
3581 +
3582 + '@esbuild/openbsd-x64@0.25.12':
3583 + optional: true
3584 +
3585 + '@esbuild/openbsd-x64@0.28.2':
3586 + optional: true
3587 +
3588 + '@esbuild/openharmony-arm64@0.25.12':
3589 + optional: true
3590 +
3591 + '@esbuild/openharmony-arm64@0.28.2':
3592 + optional: true
3593 +
3594 + '@esbuild/sunos-x64@0.18.20':
3595 + optional: true
3596 +
3597 + '@esbuild/sunos-x64@0.25.12':
3598 + optional: true
3599 +
3600 + '@esbuild/sunos-x64@0.28.2':
3601 + optional: true
3602 +
3603 + '@esbuild/win32-arm64@0.18.20':
3604 + optional: true
3605 +
3606 + '@esbuild/win32-arm64@0.25.12':
3607 + optional: true
3608 +
3609 + '@esbuild/win32-arm64@0.28.2':
3610 + optional: true
3611 +
3612 + '@esbuild/win32-ia32@0.18.20':
3613 + optional: true
3614 +
3615 + '@esbuild/win32-ia32@0.25.12':
3616 + optional: true
3617 +
3618 + '@esbuild/win32-ia32@0.28.2':
3619 + optional: true
3620 +
3621 + '@esbuild/win32-x64@0.18.20':
3622 + optional: true
3623 +
3624 + '@esbuild/win32-x64@0.25.12':
3625 + optional: true
3626 +
3627 + '@esbuild/win32-x64@0.28.2':
3628 + optional: true
3629 +
3630 + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))':
3631 + dependencies:
3632 + eslint: 9.39.5(jiti@2.7.0)
3633 + eslint-visitor-keys: 3.4.3
3634 +
3635 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))':
3636 + dependencies:
3637 + eslint: 9.39.5(jiti@2.7.0)
3638 + eslint-visitor-keys: 3.4.3
3639 +
3640 + '@eslint-community/regexpp@4.12.2': {}
3641 +
3642 + '@eslint/config-array@0.21.2':
3643 + dependencies:
3644 + '@eslint/object-schema': 2.1.7
3645 + debug: 4.4.3
3646 + minimatch: 3.1.5
3647 + transitivePeerDependencies:
3648 + - supports-color
3649 +
3650 + '@eslint/config-helpers@0.4.2':
3651 + dependencies:
3652 + '@eslint/core': 0.17.0
3653 +
3654 + '@eslint/core@0.17.0':
3655 + dependencies:
3656 + '@types/json-schema': 7.0.15
3657 +
3658 + '@eslint/eslintrc@3.3.7':
3659 + dependencies:
3660 + ajv: 6.15.0
3661 + debug: 4.4.3
3662 + espree: 10.4.0
3663 + globals: 14.0.0
3664 + ignore: 5.3.2
3665 + import-fresh: 3.3.1
3666 + js-yaml: 4.3.2
3667 + minimatch: 3.1.5
3668 + strip-json-comments: 3.1.1
3669 + transitivePeerDependencies:
3670 + - supports-color
3671 +
3672 + '@eslint/js@9.39.5': {}
3673 +
3674 + '@eslint/object-schema@2.1.7': {}
3675 +
3676 + '@eslint/plugin-kit@0.4.1':
3677 + dependencies:
3678 + '@eslint/core': 0.17.0
3679 + levn: 0.4.1
3680 +
3681 + '@humanfs/core@0.19.2':
3682 + dependencies:
3683 + '@humanfs/types': 0.15.0
3684 +
3685 + '@humanfs/node@0.16.8':
3686 + dependencies:
3687 + '@humanfs/core': 0.19.2
3688 + '@humanfs/types': 0.15.0
3689 + '@humanwhocodes/retry': 0.4.3
3690 +
3691 + '@humanfs/types@0.15.0': {}
3692 +
3693 + '@humanwhocodes/module-importer@1.0.1': {}
3694 +
3695 + '@humanwhocodes/retry@0.4.3': {}
3696 +
3697 + '@img/colour@1.1.0':
3698 + optional: true
3699 +
3700 + '@img/sharp-darwin-arm64@0.35.4':
3701 + optionalDependencies:
3702 + '@img/sharp-libvips-darwin-arm64': 1.3.3
3703 + optional: true
3704 +
3705 + '@img/sharp-darwin-x64@0.35.4':
3706 + optionalDependencies:
3707 + '@img/sharp-libvips-darwin-x64': 1.3.3
3708 + optional: true
3709 +
3710 + '@img/sharp-freebsd-wasm32@0.35.4':
3711 + dependencies:
3712 + '@img/sharp-wasm32': 0.35.4
3713 + optional: true
3714 +
3715 + '@img/sharp-libvips-darwin-arm64@1.3.3':
3716 + optional: true
3717 +
3718 + '@img/sharp-libvips-darwin-x64@1.3.3':
3719 + optional: true
3720 +
3721 + '@img/sharp-libvips-linux-arm64@1.3.3':
3722 + optional: true
3723 +
3724 + '@img/sharp-libvips-linux-arm@1.3.3':
3725 + optional: true
3726 +
3727 + '@img/sharp-libvips-linux-ppc64@1.3.3':
3728 + optional: true
3729 +
3730 + '@img/sharp-libvips-linux-riscv64@1.3.3':
3731 + optional: true
3732 +
3733 + '@img/sharp-libvips-linux-s390x@1.3.3':
3734 + optional: true
3735 +
3736 + '@img/sharp-libvips-linux-x64@1.3.3':
3737 + optional: true
3738 +
3739 + '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
3740 + optional: true
3741 +
3742 + '@img/sharp-libvips-linuxmusl-x64@1.3.3':
3743 + optional: true
3744 +
3745 + '@img/sharp-linux-arm64@0.35.4':
3746 + optionalDependencies:
3747 + '@img/sharp-libvips-linux-arm64': 1.3.3
3748 + optional: true
3749 +
3750 + '@img/sharp-linux-arm@0.35.4':
3751 + optionalDependencies:
3752 + '@img/sharp-libvips-linux-arm': 1.3.3
3753 + optional: true
3754 +
3755 + '@img/sharp-linux-ppc64@0.35.4':
3756 + optionalDependencies:
3757 + '@img/sharp-libvips-linux-ppc64': 1.3.3
3758 + optional: true
3759 +
3760 + '@img/sharp-linux-riscv64@0.35.4':
3761 + optionalDependencies:
3762 + '@img/sharp-libvips-linux-riscv64': 1.3.3
3763 + optional: true
3764 +
3765 + '@img/sharp-linux-s390x@0.35.4':
3766 + optionalDependencies:
3767 + '@img/sharp-libvips-linux-s390x': 1.3.3
3768 + optional: true
3769 +
3770 + '@img/sharp-linux-x64@0.35.4':
3771 + optionalDependencies:
3772 + '@img/sharp-libvips-linux-x64': 1.3.3
3773 + optional: true
3774 +
3775 + '@img/sharp-linuxmusl-arm64@0.35.4':
3776 + optionalDependencies:
3777 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
3778 + optional: true
3779 +
3780 + '@img/sharp-linuxmusl-x64@0.35.4':
3781 + optionalDependencies:
3782 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3
3783 + optional: true
3784 +
3785 + '@img/sharp-wasm32@0.35.4':
3786 + dependencies:
3787 + '@emnapi/runtime': 1.11.3
3788 + optional: true
3789 +
3790 + '@img/sharp-webcontainers-wasm32@0.35.4':
3791 + dependencies:
3792 + '@img/sharp-wasm32': 0.35.4
3793 + optional: true
3794 +
3795 + '@img/sharp-win32-arm64@0.35.4':
3796 + optional: true
3797 +
3798 + '@img/sharp-win32-ia32@0.35.4':
3799 + optional: true
3800 +
3801 + '@img/sharp-win32-x64@0.35.4':
3802 + optional: true
3803 +
3804 + '@jridgewell/gen-mapping@0.3.13':
3805 + dependencies:
3806 + '@jridgewell/sourcemap-codec': 1.6.0
3807 + '@jridgewell/trace-mapping': 0.3.31
3808 +
3809 + '@jridgewell/remapping@2.3.5':
3810 + dependencies:
3811 + '@jridgewell/gen-mapping': 0.3.13
3812 + '@jridgewell/trace-mapping': 0.3.31
3813 +
3814 + '@jridgewell/resolve-uri@3.1.2': {}
3815 +
3816 + '@jridgewell/sourcemap-codec@1.6.0': {}
3817 +
3818 + '@jridgewell/trace-mapping@0.3.31':
3819 + dependencies:
3820 + '@jridgewell/resolve-uri': 3.1.2
3821 + '@jridgewell/sourcemap-codec': 1.6.0
3822 +
3823 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
3824 + optional: true
3825 +
3826 + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
3827 + dependencies:
3828 + '@emnapi/core': 1.10.0
3829 + '@emnapi/runtime': 1.10.0
3830 + '@tybys/wasm-util': 0.10.3
3831 + optional: true
3832 +
3833 + '@next/env@16.3.4': {}
3834 +
3835 + '@next/eslint-plugin-next@16.3.4(eslint@9.39.5(jiti@2.7.0))':
3836 + dependencies:
3837 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0))
3838 + fast-glob: 3.3.1
3839 + transitivePeerDependencies:
3840 + - eslint
3841 +
3842 + '@next/swc-darwin-arm64@16.3.4':
3843 + optional: true
3844 +
3845 + '@next/swc-darwin-x64@16.3.4':
3846 + optional: true
3847 +
3848 + '@next/swc-linux-arm64-gnu@16.3.4':
3849 + optional: true
3850 +
3851 + '@next/swc-linux-arm64-musl@16.3.4':
3852 + optional: true
3853 +
3854 + '@next/swc-linux-x64-gnu@16.3.4':
3855 + optional: true
3856 +
3857 + '@next/swc-linux-x64-musl@16.3.4':
3858 + optional: true
3859 +
3860 + '@next/swc-win32-arm64-msvc@16.3.4':
3861 + optional: true
3862 +
3863 + '@next/swc-win32-x64-msvc@16.3.4':
3864 + optional: true
3865 +
3866 + '@nodelib/fs.scandir@2.1.5':
3867 + dependencies:
3868 + '@nodelib/fs.stat': 2.0.5
3869 + run-parallel: 1.2.0
3870 +
3871 + '@nodelib/fs.stat@2.0.5': {}
3872 +
3873 + '@nodelib/fs.walk@1.2.8':
3874 + dependencies:
3875 + '@nodelib/fs.scandir': 2.1.5
3876 + fastq: 1.20.3
3877 +
3878 + '@nolyfill/is-core-module@1.0.39': {}
3879 +
3880 + '@pinojs/redact@0.4.0': {}
3881 +
3882 + '@rollup/rollup-android-arm-eabi@4.63.1':
3883 + optional: true
3884 +
3885 + '@rollup/rollup-android-arm64@4.63.1':
3886 + optional: true
3887 +
3888 + '@rollup/rollup-darwin-arm64@4.63.1':
3889 + optional: true
3890 +
3891 + '@rollup/rollup-darwin-x64@4.63.1':
3892 + optional: true
3893 +
3894 + '@rollup/rollup-freebsd-arm64@4.63.1':
3895 + optional: true
3896 +
3897 + '@rollup/rollup-freebsd-x64@4.63.1':
3898 + optional: true
3899 +
3900 + '@rollup/rollup-linux-arm-gnueabihf@4.63.1':
3901 + optional: true
3902 +
3903 + '@rollup/rollup-linux-arm-musleabihf@4.63.1':
3904 + optional: true
3905 +
3906 + '@rollup/rollup-linux-arm64-gnu@4.63.1':
3907 + optional: true
3908 +
3909 + '@rollup/rollup-linux-arm64-musl@4.63.1':
3910 + optional: true
3911 +
3912 + '@rollup/rollup-linux-loong64-gnu@4.63.1':
3913 + optional: true
3914 +
3915 + '@rollup/rollup-linux-loong64-musl@4.63.1':
3916 + optional: true
3917 +
3918 + '@rollup/rollup-linux-ppc64-gnu@4.63.1':
3919 + optional: true
3920 +
3921 + '@rollup/rollup-linux-ppc64-musl@4.63.1':
3922 + optional: true
3923 +
3924 + '@rollup/rollup-linux-riscv64-gnu@4.63.1':
3925 + optional: true
3926 +
3927 + '@rollup/rollup-linux-riscv64-musl@4.63.1':
3928 + optional: true
3929 +
3930 + '@rollup/rollup-linux-s390x-gnu@4.63.1':
3931 + optional: true
3932 +
3933 + '@rollup/rollup-linux-x64-gnu@4.63.1':
3934 + optional: true
3935 +
3936 + '@rollup/rollup-linux-x64-musl@4.63.1':
3937 + optional: true
3938 +
3939 + '@rollup/rollup-openbsd-x64@4.63.1':
3940 + optional: true
3941 +
3942 + '@rollup/rollup-openharmony-arm64@4.63.1':
3943 + optional: true
3944 +
3945 + '@rollup/rollup-win32-arm64-msvc@4.63.1':
3946 + optional: true
3947 +
3948 + '@rollup/rollup-win32-ia32-msvc@4.63.1':
3949 + optional: true
3950 +
3951 + '@rollup/rollup-win32-x64-gnu@4.63.1':
3952 + optional: true
3953 +
3954 + '@rollup/rollup-win32-x64-msvc@4.63.1':
3955 + optional: true
3956 +
3957 + '@rtsao/scc@1.1.0': {}
3958 +
3959 + '@swc/helpers@0.5.23':
3960 + dependencies:
3961 + tslib: 2.8.1
3962 +
3963 + '@tailwindcss/node@4.3.3':
3964 + dependencies:
3965 + '@jridgewell/remapping': 2.3.5
3966 + enhanced-resolve: 5.24.5
3967 + jiti: 2.7.0
3968 + lightningcss: 1.32.0
3969 + magic-string: 0.30.21
3970 + source-map-js: 1.2.1
3971 + tailwindcss: 4.3.3
3972 +
3973 + '@tailwindcss/oxide-android-arm64@4.3.3':
3974 + optional: true
3975 +
3976 + '@tailwindcss/oxide-darwin-arm64@4.3.3':
3977 + optional: true
3978 +
3979 + '@tailwindcss/oxide-darwin-x64@4.3.3':
3980 + optional: true
3981 +
3982 + '@tailwindcss/oxide-freebsd-x64@4.3.3':
3983 + optional: true
3984 +
3985 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3':
3986 + optional: true
3987 +
3988 + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3':
3989 + optional: true
3990 +
3991 + '@tailwindcss/oxide-linux-arm64-musl@4.3.3':
3992 + optional: true
3993 +
3994 + '@tailwindcss/oxide-linux-x64-gnu@4.3.3':
3995 + optional: true
3996 +
3997 + '@tailwindcss/oxide-linux-x64-musl@4.3.3':
3998 + optional: true
3999 +
4000 + '@tailwindcss/oxide-wasm32-wasi@4.3.3':
4001 + optional: true
4002 +
4003 + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3':
4004 + optional: true
4005 +
4006 + '@tailwindcss/oxide-win32-x64-msvc@4.3.3':
4007 + optional: true
4008 +
4009 + '@tailwindcss/oxide@4.3.3':
4010 + optionalDependencies:
4011 + '@tailwindcss/oxide-android-arm64': 4.3.3
4012 + '@tailwindcss/oxide-darwin-arm64': 4.3.3
4013 + '@tailwindcss/oxide-darwin-x64': 4.3.3
4014 + '@tailwindcss/oxide-freebsd-x64': 4.3.3
4015 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3
4016 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3
4017 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3
4018 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3
4019 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3
4020 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3
4021 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3
4022 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3
4023 +
4024 + '@tailwindcss/postcss@4.3.3':
4025 + dependencies:
4026 + '@alloc/quick-lru': 5.3.0
4027 + '@tailwindcss/node': 4.3.3
4028 + '@tailwindcss/oxide': 4.3.3
4029 + postcss: 8.5.28
4030 + tailwindcss: 4.3.3
4031 +
4032 + '@tanstack/query-core@5.102.8': {}
4033 +
4034 + '@tanstack/react-query@5.102.8(react@19.2.8)':
4035 + dependencies:
4036 + '@tanstack/query-core': 5.102.8
4037 + react: 19.2.8
4038 +
4039 + '@tybys/wasm-util@0.10.3':
4040 + dependencies:
4041 + tslib: 2.8.1
4042 + optional: true
4043 +
4044 + '@types/chai@5.2.3':
4045 + dependencies:
4046 + '@types/deep-eql': 4.0.2
4047 + assertion-error: 2.0.1
4048 +
4049 + '@types/deep-eql@4.0.2': {}
4050 +
4051 + '@types/estree@1.0.9': {}
4052 +
4053 + '@types/json-schema@7.0.15': {}
4054 +
4055 + '@types/json5@0.0.29': {}
4056 +
4057 + '@types/node@24.13.3':
4058 + dependencies:
4059 + undici-types: 7.18.2
4060 +
4061 + '@types/react-dom@19.2.7(@types/react@19.2.18)':
4062 + dependencies:
4063 + '@types/react': 19.2.18
4064 +
4065 + '@types/react@19.2.18':
4066 + dependencies:
4067 + csstype: 3.2.3
4068 +
4069 + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
4070 + dependencies:
4071 + '@eslint-community/regexpp': 4.12.2
4072 + '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4073 + '@typescript-eslint/scope-manager': 8.69.0
4074 + '@typescript-eslint/type-utils': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4075 + '@typescript-eslint/utils': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4076 + '@typescript-eslint/visitor-keys': 8.69.0
4077 + eslint: 9.39.5(jiti@2.7.0)
4078 + ignore: 7.0.8
4079 + natural-compare: 1.4.0
4080 + ts-api-utils: 2.5.0(typescript@5.9.3)
4081 + typescript: 5.9.3
4082 + transitivePeerDependencies:
4083 + - supports-color
4084 +
4085 + '@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
4086 + dependencies:
4087 + '@typescript-eslint/scope-manager': 8.69.0
4088 + '@typescript-eslint/types': 8.69.0
4089 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3)
4090 + '@typescript-eslint/visitor-keys': 8.69.0
4091 + debug: 4.4.3
4092 + eslint: 9.39.5(jiti@2.7.0)
4093 + typescript: 5.9.3
4094 + transitivePeerDependencies:
4095 + - supports-color
4096 +
4097 + '@typescript-eslint/project-service@8.69.0(typescript@5.9.3)':
4098 + dependencies:
4099 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@5.9.3)
4100 + '@typescript-eslint/types': 8.69.0
4101 + debug: 4.4.3
4102 + typescript: 5.9.3
4103 + transitivePeerDependencies:
4104 + - supports-color
4105 +
4106 + '@typescript-eslint/scope-manager@8.69.0':
4107 + dependencies:
4108 + '@typescript-eslint/types': 8.69.0
4109 + '@typescript-eslint/visitor-keys': 8.69.0
4110 +
4111 + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@5.9.3)':
4112 + dependencies:
4113 + typescript: 5.9.3
4114 +
4115 + '@typescript-eslint/type-utils@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
4116 + dependencies:
4117 + '@typescript-eslint/types': 8.69.0
4118 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3)
4119 + '@typescript-eslint/utils': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4120 + debug: 4.4.3
4121 + eslint: 9.39.5(jiti@2.7.0)
4122 + ts-api-utils: 2.5.0(typescript@5.9.3)
4123 + typescript: 5.9.3
4124 + transitivePeerDependencies:
4125 + - supports-color
4126 +
4127 + '@typescript-eslint/types@8.69.0': {}
4128 +
4129 + '@typescript-eslint/typescript-estree@8.69.0(typescript@5.9.3)':
4130 + dependencies:
4131 + '@typescript-eslint/project-service': 8.69.0(typescript@5.9.3)
4132 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@5.9.3)
4133 + '@typescript-eslint/types': 8.69.0
4134 + '@typescript-eslint/visitor-keys': 8.69.0
4135 + debug: 4.4.3
4136 + minimatch: 10.2.6
4137 + semver: 7.8.5
4138 + tinyglobby: 0.2.17
4139 + ts-api-utils: 2.5.0(typescript@5.9.3)
4140 + typescript: 5.9.3
4141 + transitivePeerDependencies:
4142 + - supports-color
4143 +
4144 + '@typescript-eslint/utils@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
4145 + dependencies:
4146 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
4147 + '@typescript-eslint/scope-manager': 8.69.0
4148 + '@typescript-eslint/types': 8.69.0
4149 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3)
4150 + eslint: 9.39.5(jiti@2.7.0)
4151 + typescript: 5.9.3
4152 + transitivePeerDependencies:
4153 + - supports-color
4154 +
4155 + '@typescript-eslint/visitor-keys@8.69.0':
4156 + dependencies:
4157 + '@typescript-eslint/types': 8.69.0
4158 + eslint-visitor-keys: 5.0.1
4159 +
4160 + '@unrs/resolver-binding-android-arm-eabi@1.12.2':
4161 + optional: true
4162 +
4163 + '@unrs/resolver-binding-android-arm64@1.12.2':
4164 + optional: true
4165 +
4166 + '@unrs/resolver-binding-darwin-arm64@1.12.2':
4167 + optional: true
4168 +
4169 + '@unrs/resolver-binding-darwin-x64@1.12.2':
4170 + optional: true
4171 +
4172 + '@unrs/resolver-binding-freebsd-x64@1.12.2':
4173 + optional: true
4174 +
4175 + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
4176 + optional: true
4177 +
4178 + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
4179 + optional: true
4180 +
4181 + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
4182 + optional: true
4183 +
4184 + '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
4185 + optional: true
4186 +
4187 + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
4188 + optional: true
4189 +
4190 + '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
4191 + optional: true
4192 +
4193 + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
4194 + optional: true
4195 +
4196 + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
4197 + optional: true
4198 +
4199 + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
4200 + optional: true
4201 +
4202 + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
4203 + optional: true
4204 +
4205 + '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
4206 + optional: true
4207 +
4208 + '@unrs/resolver-binding-linux-x64-musl@1.12.2':
4209 + optional: true
4210 +
4211 + '@unrs/resolver-binding-openharmony-arm64@1.12.2':
4212 + optional: true
4213 +
4214 + '@unrs/resolver-binding-wasm32-wasi@1.12.2':
4215 + dependencies:
4216 + '@emnapi/core': 1.10.0
4217 + '@emnapi/runtime': 1.10.0
4218 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
4219 + optional: true
4220 +
4221 + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
4222 + optional: true
4223 +
4224 + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
4225 + optional: true
4226 +
4227 + '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
4228 + optional: true
4229 +
4230 + '@vitest/expect@3.2.7':
4231 + dependencies:
4232 + '@types/chai': 5.2.3
4233 + '@vitest/spy': 3.2.7
4234 + '@vitest/utils': 3.2.7
4235 + chai: 5.3.3
4236 + tinyrainbow: 2.0.0
4237 +
4238 + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13))':
4239 + dependencies:
4240 + '@vitest/spy': 3.2.7
4241 + estree-walker: 3.0.3
4242 + magic-string: 0.30.21
4243 + optionalDependencies:
4244 + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
4245 +
4246 + '@vitest/pretty-format@3.2.7':
4247 + dependencies:
4248 + tinyrainbow: 2.0.0
4249 +
4250 + '@vitest/runner@3.2.7':
4251 + dependencies:
4252 + '@vitest/utils': 3.2.7
4253 + pathe: 2.0.3
4254 + strip-literal: 3.1.0
4255 +
4256 + '@vitest/snapshot@3.2.7':
4257 + dependencies:
4258 + '@vitest/pretty-format': 3.2.7
4259 + magic-string: 0.30.21
4260 + pathe: 2.0.3
4261 +
4262 + '@vitest/spy@3.2.7':
4263 + dependencies:
4264 + tinyspy: 4.0.6
4265 +
4266 + '@vitest/utils@3.2.7':
4267 + dependencies:
4268 + '@vitest/pretty-format': 3.2.7
4269 + loupe: 3.2.1
4270 + tinyrainbow: 2.0.0
4271 +
4272 + acorn-jsx@5.3.2(acorn@8.18.0):
4273 + dependencies:
4274 + acorn: 8.18.0
4275 +
4276 + acorn@8.18.0: {}
4277 +
4278 + ajv@6.15.0:
4279 + dependencies:
4280 + fast-deep-equal: 3.1.3
4281 + fast-json-stable-stringify: 2.1.0
4282 + json-schema-traverse: 0.4.1
4283 + uri-js: 4.4.1
4284 +
4285 + ansi-styles@4.3.0:
4286 + dependencies:
4287 + color-convert: 2.0.1
4288 +
4289 + argparse@2.0.1: {}
4290 +
4291 + aria-query@5.3.2: {}
4292 +
4293 + array-buffer-byte-length@1.0.2:
4294 + dependencies:
4295 + call-bound: 1.0.4
4296 + is-array-buffer: 3.0.5
4297 +
4298 + array-includes@3.1.9:
4299 + dependencies:
4300 + call-bind: 1.0.9
4301 + call-bound: 1.0.4
4302 + define-properties: 1.2.1
4303 + es-abstract: 1.24.2
4304 + es-object-atoms: 1.1.2
4305 + get-intrinsic: 1.3.0
4306 + is-string: 1.1.1
4307 + math-intrinsics: 1.1.0
4308 +
4309 + array.prototype.findlast@1.2.5:
4310 + dependencies:
4311 + call-bind: 1.0.9
4312 + define-properties: 1.2.1
4313 + es-abstract: 1.24.2
4314 + es-errors: 1.3.0
4315 + es-object-atoms: 1.1.2
4316 + es-shim-unscopables: 1.1.0
4317 +
4318 + array.prototype.findlastindex@1.2.6:
4319 + dependencies:
4320 + call-bind: 1.0.9
4321 + call-bound: 1.0.4
4322 + define-properties: 1.2.1
4323 + es-abstract: 1.24.2
4324 + es-errors: 1.3.0
4325 + es-object-atoms: 1.1.2
4326 + es-shim-unscopables: 1.1.0
4327 +
4328 + array.prototype.flat@1.3.3:
4329 + dependencies:
4330 + call-bind: 1.0.9
4331 + define-properties: 1.2.1
4332 + es-abstract: 1.24.2
4333 + es-shim-unscopables: 1.1.0
4334 +
4335 + array.prototype.flatmap@1.3.3:
4336 + dependencies:
4337 + call-bind: 1.0.9
4338 + define-properties: 1.2.1
4339 + es-abstract: 1.24.2
4340 + es-shim-unscopables: 1.1.0
4341 +
4342 + array.prototype.tosorted@1.1.4:
4343 + dependencies:
4344 + call-bind: 1.0.9
4345 + define-properties: 1.2.1
4346 + es-abstract: 1.24.2
4347 + es-errors: 1.3.0
4348 + es-shim-unscopables: 1.1.0
4349 +
4350 + arraybuffer.prototype.slice@1.0.4:
4351 + dependencies:
4352 + array-buffer-byte-length: 1.0.2
4353 + call-bind: 1.0.9
4354 + define-properties: 1.2.1
4355 + es-abstract: 1.24.2
4356 + es-errors: 1.3.0
4357 + get-intrinsic: 1.3.0
4358 + is-array-buffer: 3.0.5
4359 +
4360 + assertion-error@2.0.1: {}
4361 +
4362 + ast-types-flow@0.0.8: {}
4363 +
4364 + async-function@1.0.0: {}
4365 +
4366 + atomic-sleep@1.0.0: {}
4367 +
4368 + available-typed-arrays@1.0.7:
4369 + dependencies:
4370 + possible-typed-array-names: 1.1.0
4371 +
4372 + axe-core@4.13.0: {}
4373 +
4374 + axobject-query@4.1.0: {}
4375 +
4376 + balanced-match@1.0.2: {}
4377 +
4378 + balanced-match@4.0.4: {}
4379 +
4380 + baseline-browser-mapping@2.11.21: {}
4381 +
4382 + boolbase@1.0.0: {}
4383 +
4384 + brace-expansion@1.1.18:
4385 + dependencies:
4386 + balanced-match: 1.0.2
4387 + concat-map: 0.0.1
4388 +
4389 + brace-expansion@5.0.9:
4390 + dependencies:
4391 + balanced-match: 4.0.4
4392 +
4393 + braces@3.0.3:
4394 + dependencies:
4395 + fill-range: 7.1.1
4396 +
4397 + browserslist@4.28.9:
4398 + dependencies:
4399 + baseline-browser-mapping: 2.11.21
4400 + caniuse-lite: 1.0.30001810
4401 + electron-to-chromium: 1.5.422
4402 + node-releases: 2.0.54
4403 + update-browserslist-db: 1.3.2(browserslist@4.28.9)
4404 +
4405 + buffer-from@1.1.2: {}
4406 +
4407 + cac@6.7.14: {}
4408 +
4409 + call-bind-apply-helpers@1.0.2:
4410 + dependencies:
4411 + es-errors: 1.3.0
4412 + function-bind: 1.1.2
4413 +
4414 + call-bind@1.0.9:
4415 + dependencies:
4416 + call-bind-apply-helpers: 1.0.2
4417 + es-define-property: 1.0.1
4418 + get-intrinsic: 1.3.0
4419 + set-function-length: 1.2.2
4420 +
4421 + call-bound@1.0.4:
4422 + dependencies:
4423 + call-bind-apply-helpers: 1.0.2
4424 + get-intrinsic: 1.3.0
4425 +
4426 + callsites@3.1.0: {}
4427 +
4428 + caniuse-lite@1.0.30001810: {}
4429 +
4430 + chai@5.3.3:
4431 + dependencies:
4432 + assertion-error: 2.0.1
4433 + check-error: 2.1.3
4434 + deep-eql: 5.0.2
4435 + loupe: 3.2.1
4436 + pathval: 2.0.1
4437 +
4438 + chalk@4.1.2:
4439 + dependencies:
4440 + ansi-styles: 4.3.0
4441 + supports-color: 7.2.0
4442 +
4443 + check-error@2.1.3: {}
4444 +
4445 + cheerio-select@2.1.0:
4446 + dependencies:
4447 + boolbase: 1.0.0
4448 + css-select: 5.2.2
4449 + css-what: 6.2.2
4450 + domelementtype: 2.3.0
4451 + domhandler: 5.0.3
4452 + domutils: 3.2.2
4453 +
4454 + cheerio@1.2.0:
4455 + dependencies:
4456 + cheerio-select: 2.1.0
4457 + dom-serializer: 2.0.0
4458 + domhandler: 5.0.3
4459 + domutils: 3.2.2
4460 + encoding-sniffer: 0.2.1
4461 + htmlparser2: 10.1.0
4462 + parse5: 7.3.0
4463 + parse5-htmlparser2-tree-adapter: 7.1.0
4464 + parse5-parser-stream: 7.1.2
4465 + undici: 7.29.1
4466 + whatwg-mimetype: 4.0.0
4467 +
4468 + client-only@0.0.1: {}
4469 +
4470 + color-convert@2.0.1:
4471 + dependencies:
4472 + color-name: 1.1.4
4473 +
4474 + color-name@1.1.4: {}
4475 +
4476 + concat-map@0.0.1: {}
4477 +
4478 + convert-source-map@2.0.0: {}
4479 +
4480 + cross-spawn@7.0.6:
4481 + dependencies:
4482 + path-key: 3.1.1
4483 + shebang-command: 2.0.0
4484 + which: 2.0.2
4485 +
4486 + css-select@5.2.2:
4487 + dependencies:
4488 + boolbase: 1.0.0
4489 + css-what: 6.2.2
4490 + domhandler: 5.0.3
4491 + domutils: 3.2.2
4492 + nth-check: 2.1.1
4493 +
4494 + css-what@6.2.2: {}
4495 +
4496 + csstype@3.2.3: {}
4497 +
4498 + damerau-levenshtein@1.0.8: {}
4499 +
4500 + data-view-buffer@1.0.2:
4501 + dependencies:
4502 + call-bound: 1.0.4
4503 + es-errors: 1.3.0
4504 + is-data-view: 1.0.2
4505 +
4506 + data-view-byte-length@1.0.2:
4507 + dependencies:
4508 + call-bound: 1.0.4
4509 + es-errors: 1.3.0
4510 + is-data-view: 1.0.2
4511 +
4512 + data-view-byte-offset@1.0.1:
4513 + dependencies:
4514 + call-bound: 1.0.4
4515 + es-errors: 1.3.0
4516 + is-data-view: 1.0.2
4517 +
4518 + debug@3.2.7:
4519 + dependencies:
4520 + ms: 2.1.3
4521 +
4522 + debug@4.4.3:
4523 + dependencies:
4524 + ms: 2.1.3
4525 +
4526 + deep-eql@5.0.2: {}
4527 +
4528 + deep-is@0.1.4: {}
4529 +
4530 + define-data-property@1.1.4:
4531 + dependencies:
4532 + es-define-property: 1.0.1
4533 + es-errors: 1.3.0
4534 + gopd: 1.2.0
4535 +
4536 + define-properties@1.2.1:
4537 + dependencies:
4538 + define-data-property: 1.1.4
4539 + has-property-descriptors: 1.0.2
4540 + object-keys: 1.1.1
4541 +
4542 + detect-libc@2.1.2: {}
4543 +
4544 + doctrine@2.1.0:
4545 + dependencies:
4546 + esutils: 2.0.3
4547 +
4548 + dom-serializer@2.0.0:
4549 + dependencies:
4550 + domelementtype: 2.3.0
4551 + domhandler: 5.0.3
4552 + entities: 4.5.0
4553 +
4554 + domelementtype@2.3.0: {}
4555 +
4556 + domhandler@5.0.3:
4557 + dependencies:
4558 + domelementtype: 2.3.0
4559 +
4560 + domutils@3.2.2:
4561 + dependencies:
4562 + dom-serializer: 2.0.0
4563 + domelementtype: 2.3.0
4564 + domhandler: 5.0.3
4565 +
4566 + drizzle-kit@0.31.10:
4567 + dependencies:
4568 + '@drizzle-team/brocli': 0.10.2
4569 + '@esbuild-kit/esm-loader': 2.6.5
4570 + esbuild: 0.25.12
4571 + tsx: 4.23.13
4572 +
4573 + drizzle-orm@0.45.2(postgres@3.4.9):
4574 + optionalDependencies:
4575 + postgres: 3.4.9
4576 +
4577 + dunder-proto@1.0.1:
4578 + dependencies:
4579 + call-bind-apply-helpers: 1.0.2
4580 + es-errors: 1.3.0
4581 + gopd: 1.2.0
4582 +
4583 + electron-to-chromium@1.5.422: {}
4584 +
4585 + emoji-regex@9.2.2: {}
4586 +
4587 + encoding-sniffer@0.2.1:
4588 + dependencies:
4589 + iconv-lite: 0.6.3
4590 + whatwg-encoding: 3.1.1
4591 +
4592 + enhanced-resolve@5.24.5:
4593 + dependencies:
4594 + graceful-fs: 4.2.11
4595 + tapable: 2.3.3
4596 +
4597 + entities@4.5.0: {}
4598 +
4599 + entities@6.0.1: {}
4600 +
4601 + entities@7.0.1: {}
4602 +
4603 + es-abstract-get@1.0.0:
4604 + dependencies:
4605 + es-errors: 1.3.0
4606 + es-object-atoms: 1.1.2
4607 + is-callable: 1.2.7
4608 + object-inspect: 1.13.4
4609 +
4610 + es-abstract@1.24.2:
4611 + dependencies:
4612 + array-buffer-byte-length: 1.0.2
4613 + arraybuffer.prototype.slice: 1.0.4
4614 + available-typed-arrays: 1.0.7
4615 + call-bind: 1.0.9
4616 + call-bound: 1.0.4
4617 + data-view-buffer: 1.0.2
4618 + data-view-byte-length: 1.0.2
4619 + data-view-byte-offset: 1.0.1
4620 + es-define-property: 1.0.1
4621 + es-errors: 1.3.0
4622 + es-object-atoms: 1.1.2
4623 + es-set-tostringtag: 2.1.0
4624 + es-to-primitive: 1.3.4
4625 + function.prototype.name: 1.2.0
4626 + get-intrinsic: 1.3.0
4627 + get-proto: 1.0.1
4628 + get-symbol-description: 1.1.0
4629 + globalthis: 1.0.4
4630 + gopd: 1.2.0
4631 + has-property-descriptors: 1.0.2
4632 + has-proto: 1.2.0
4633 + has-symbols: 1.1.0
4634 + hasown: 2.0.4
4635 + internal-slot: 1.1.0
4636 + is-array-buffer: 3.0.5
4637 + is-callable: 1.2.7
4638 + is-data-view: 1.0.2
4639 + is-negative-zero: 2.0.3
4640 + is-regex: 1.2.1
4641 + is-set: 2.0.3
4642 + is-shared-array-buffer: 1.0.4
4643 + is-string: 1.1.1
4644 + is-typed-array: 1.1.15
4645 + is-weakref: 1.1.1
4646 + math-intrinsics: 1.1.0
4647 + object-inspect: 1.13.4
4648 + object-keys: 1.1.1
4649 + object.assign: 4.1.7
4650 + own-keys: 1.0.2
4651 + regexp.prototype.flags: 1.5.4
4652 + safe-array-concat: 1.1.4
4653 + safe-push-apply: 1.0.0
4654 + safe-regex-test: 1.1.0
4655 + set-proto: 1.0.0
4656 + stop-iteration-iterator: 1.1.0
4657 + string.prototype.trim: 1.2.11
4658 + string.prototype.trimend: 1.0.10
4659 + string.prototype.trimstart: 1.0.8
4660 + typed-array-buffer: 1.0.3
4661 + typed-array-byte-length: 1.0.3
4662 + typed-array-byte-offset: 1.0.4
4663 + typed-array-length: 1.0.8
4664 + unbox-primitive: 1.1.0
4665 + which-typed-array: 1.1.22
4666 +
4667 + es-define-property@1.0.1: {}
4668 +
4669 + es-errors@1.3.0: {}
4670 +
4671 + es-iterator-helpers@1.4.0:
4672 + dependencies:
4673 + call-bind: 1.0.9
4674 + call-bound: 1.0.4
4675 + define-properties: 1.2.1
4676 + es-abstract: 1.24.2
4677 + es-errors: 1.3.0
4678 + es-set-tostringtag: 2.1.0
4679 + function-bind: 1.1.2
4680 + get-intrinsic: 1.3.0
4681 + globalthis: 1.0.4
4682 + gopd: 1.2.0
4683 + has-property-descriptors: 1.0.2
4684 + has-proto: 1.2.0
4685 + has-symbols: 1.1.0
4686 + internal-slot: 1.1.0
4687 + iterator.prototype: 1.1.5
4688 + math-intrinsics: 1.1.0
4689 +
4690 + es-module-lexer@1.7.0: {}
4691 +
4692 + es-object-atoms@1.1.2:
4693 + dependencies:
4694 + es-errors: 1.3.0
4695 +
4696 + es-set-tostringtag@2.1.0:
4697 + dependencies:
4698 + es-errors: 1.3.0
4699 + get-intrinsic: 1.3.0
4700 + has-tostringtag: 1.0.2
4701 + hasown: 2.0.4
4702 +
4703 + es-shim-unscopables@1.1.0:
4704 + dependencies:
4705 + hasown: 2.0.4
4706 +
4707 + es-to-primitive@1.3.4:
4708 + dependencies:
4709 + es-abstract-get: 1.0.0
4710 + es-define-property: 1.0.1
4711 + es-errors: 1.3.0
4712 + is-callable: 1.2.7
4713 + is-date-object: 1.1.0
4714 + is-symbol: 1.1.1
4715 +
4716 + esbuild@0.18.20:
4717 + optionalDependencies:
4718 + '@esbuild/android-arm': 0.18.20
4719 + '@esbuild/android-arm64': 0.18.20
4720 + '@esbuild/android-x64': 0.18.20
4721 + '@esbuild/darwin-arm64': 0.18.20
4722 + '@esbuild/darwin-x64': 0.18.20
4723 + '@esbuild/freebsd-arm64': 0.18.20
4724 + '@esbuild/freebsd-x64': 0.18.20
4725 + '@esbuild/linux-arm': 0.18.20
4726 + '@esbuild/linux-arm64': 0.18.20
4727 + '@esbuild/linux-ia32': 0.18.20
4728 + '@esbuild/linux-loong64': 0.18.20
4729 + '@esbuild/linux-mips64el': 0.18.20
4730 + '@esbuild/linux-ppc64': 0.18.20
4731 + '@esbuild/linux-riscv64': 0.18.20
4732 + '@esbuild/linux-s390x': 0.18.20
4733 + '@esbuild/linux-x64': 0.18.20
4734 + '@esbuild/netbsd-x64': 0.18.20
4735 + '@esbuild/openbsd-x64': 0.18.20
4736 + '@esbuild/sunos-x64': 0.18.20
4737 + '@esbuild/win32-arm64': 0.18.20
4738 + '@esbuild/win32-ia32': 0.18.20
4739 + '@esbuild/win32-x64': 0.18.20
4740 +
4741 + esbuild@0.25.12:
4742 + optionalDependencies:
4743 + '@esbuild/aix-ppc64': 0.25.12
4744 + '@esbuild/android-arm': 0.25.12
4745 + '@esbuild/android-arm64': 0.25.12
4746 + '@esbuild/android-x64': 0.25.12
4747 + '@esbuild/darwin-arm64': 0.25.12
4748 + '@esbuild/darwin-x64': 0.25.12
4749 + '@esbuild/freebsd-arm64': 0.25.12
4750 + '@esbuild/freebsd-x64': 0.25.12
4751 + '@esbuild/linux-arm': 0.25.12
4752 + '@esbuild/linux-arm64': 0.25.12
4753 + '@esbuild/linux-ia32': 0.25.12
4754 + '@esbuild/linux-loong64': 0.25.12
4755 + '@esbuild/linux-mips64el': 0.25.12
4756 + '@esbuild/linux-ppc64': 0.25.12
4757 + '@esbuild/linux-riscv64': 0.25.12
4758 + '@esbuild/linux-s390x': 0.25.12
4759 + '@esbuild/linux-x64': 0.25.12
4760 + '@esbuild/netbsd-arm64': 0.25.12
4761 + '@esbuild/netbsd-x64': 0.25.12
4762 + '@esbuild/openbsd-arm64': 0.25.12
4763 + '@esbuild/openbsd-x64': 0.25.12
4764 + '@esbuild/openharmony-arm64': 0.25.12
4765 + '@esbuild/sunos-x64': 0.25.12
4766 + '@esbuild/win32-arm64': 0.25.12
4767 + '@esbuild/win32-ia32': 0.25.12
4768 + '@esbuild/win32-x64': 0.25.12
4769 +
4770 + esbuild@0.28.2:
4771 + optionalDependencies:
4772 + '@esbuild/aix-ppc64': 0.28.2
4773 + '@esbuild/android-arm': 0.28.2
4774 + '@esbuild/android-arm64': 0.28.2
4775 + '@esbuild/android-x64': 0.28.2
4776 + '@esbuild/darwin-arm64': 0.28.2
4777 + '@esbuild/darwin-x64': 0.28.2
4778 + '@esbuild/freebsd-arm64': 0.28.2
4779 + '@esbuild/freebsd-x64': 0.28.2
4780 + '@esbuild/linux-arm': 0.28.2
4781 + '@esbuild/linux-arm64': 0.28.2
4782 + '@esbuild/linux-ia32': 0.28.2
4783 + '@esbuild/linux-loong64': 0.28.2
4784 + '@esbuild/linux-mips64el': 0.28.2
4785 + '@esbuild/linux-ppc64': 0.28.2
4786 + '@esbuild/linux-riscv64': 0.28.2
4787 + '@esbuild/linux-s390x': 0.28.2
4788 + '@esbuild/linux-x64': 0.28.2
4789 + '@esbuild/netbsd-arm64': 0.28.2
4790 + '@esbuild/netbsd-x64': 0.28.2
4791 + '@esbuild/openbsd-arm64': 0.28.2
4792 + '@esbuild/openbsd-x64': 0.28.2
4793 + '@esbuild/openharmony-arm64': 0.28.2
4794 + '@esbuild/sunos-x64': 0.28.2
4795 + '@esbuild/win32-arm64': 0.28.2
4796 + '@esbuild/win32-ia32': 0.28.2
4797 + '@esbuild/win32-x64': 0.28.2
4798 +
4799 + escalade@3.2.0: {}
4800 +
4801 + escape-string-regexp@4.0.0: {}
4802 +
4803 + eslint-config-next@16.3.4(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3):
4804 + dependencies:
4805 + '@next/eslint-plugin-next': 16.3.4(eslint@9.39.5(jiti@2.7.0))
4806 + eslint: 9.39.5(jiti@2.7.0)
4807 + eslint-import-resolver-node: 0.3.10
4808 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))
4809 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0))
4810 + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0))
4811 + eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0))
4812 + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0))
4813 + globals: 16.4.0
4814 + typescript-eslint: 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4815 + optionalDependencies:
4816 + typescript: 5.9.3
4817 + transitivePeerDependencies:
4818 + - '@typescript-eslint/parser'
4819 + - eslint-import-resolver-webpack
4820 + - eslint-plugin-import-x
4821 + - supports-color
4822 +
4823 + eslint-import-resolver-node@0.3.10:
4824 + dependencies:
4825 + debug: 3.2.7
4826 + is-core-module: 2.16.2
4827 + resolve: 2.0.0-next.7
4828 + transitivePeerDependencies:
4829 + - supports-color
4830 +
4831 + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)):
4832 + dependencies:
4833 + '@nolyfill/is-core-module': 1.0.39
4834 + debug: 4.4.3
4835 + eslint: 9.39.5(jiti@2.7.0)
4836 + get-tsconfig: 4.14.3
4837 + is-bun-module: 2.0.0
4838 + stable-hash: 0.0.5
4839 + tinyglobby: 0.2.17
4840 + unrs-resolver: 1.12.2
4841 + optionalDependencies:
4842 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0))
4843 + transitivePeerDependencies:
4844 + - supports-color
4845 +
4846 + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)):
4847 + dependencies:
4848 + debug: 3.2.7
4849 + optionalDependencies:
4850 + '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4851 + eslint: 9.39.5(jiti@2.7.0)
4852 + eslint-import-resolver-node: 0.3.10
4853 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))
4854 + transitivePeerDependencies:
4855 + - supports-color
4856 +
4857 + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)):
4858 + dependencies:
4859 + '@rtsao/scc': 1.1.0
4860 + array-includes: 3.1.9
4861 + array.prototype.findlastindex: 1.2.6
4862 + array.prototype.flat: 1.3.3
4863 + array.prototype.flatmap: 1.3.3
4864 + debug: 3.2.7
4865 + doctrine: 2.1.0
4866 + eslint: 9.39.5(jiti@2.7.0)
4867 + eslint-import-resolver-node: 0.3.10
4868 + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))
4869 + hasown: 2.0.4
4870 + is-core-module: 2.16.2
4871 + is-glob: 4.0.3
4872 + minimatch: 3.1.5
4873 + object.fromentries: 2.0.8
4874 + object.groupby: 1.0.3
4875 + object.values: 1.2.1
4876 + semver: 6.3.1
4877 + string.prototype.trimend: 1.0.10
4878 + tsconfig-paths: 3.15.0
4879 + optionalDependencies:
4880 + '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
4881 + transitivePeerDependencies:
4882 + - eslint-import-resolver-typescript
4883 + - eslint-import-resolver-webpack
4884 + - supports-color
4885 +
4886 + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)):
4887 + dependencies:
4888 + aria-query: 5.3.2
4889 + array-includes: 3.1.9
4890 + array.prototype.flatmap: 1.3.3
4891 + ast-types-flow: 0.0.8
4892 + axe-core: 4.13.0
4893 + axobject-query: 4.1.0
4894 + damerau-levenshtein: 1.0.8
4895 + emoji-regex: 9.2.2
4896 + eslint: 9.39.5(jiti@2.7.0)
4897 + hasown: 2.0.4
4898 + jsx-ast-utils: 3.3.5
4899 + language-tags: 1.0.9
4900 + minimatch: 3.1.5
4901 + object.fromentries: 2.0.8
4902 + safe-regex-test: 1.1.0
4903 + string.prototype.includes: 2.0.1
4904 +
4905 + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)):
4906 + dependencies:
4907 + '@babel/core': 7.29.7
4908 + '@babel/parser': 7.29.8
4909 + eslint: 9.39.5(jiti@2.7.0)
4910 + hermes-parser: 0.25.1
4911 + zod: 4.5.4
4912 + zod-validation-error: 4.0.2(zod@4.5.4)
4913 + transitivePeerDependencies:
4914 + - supports-color
4915 +
4916 + eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)):
4917 + dependencies:
4918 + array-includes: 3.1.9
4919 + array.prototype.findlast: 1.2.5
4920 + array.prototype.flatmap: 1.3.3
4921 + array.prototype.tosorted: 1.1.4
4922 + doctrine: 2.1.0
4923 + es-iterator-helpers: 1.4.0
4924 + eslint: 9.39.5(jiti@2.7.0)
4925 + estraverse: 5.3.0
4926 + hasown: 2.0.4
4927 + jsx-ast-utils: 3.3.5
4928 + minimatch: 3.1.5
4929 + object.entries: 1.1.9
4930 + object.fromentries: 2.0.8
4931 + object.values: 1.2.1
4932 + prop-types: 15.8.1
4933 + resolve: 2.0.0-next.7
4934 + semver: 6.3.1
4935 + string.prototype.matchall: 4.1.0
4936 + string.prototype.repeat: 1.0.0
4937 +
4938 + eslint-scope@8.4.0:
4939 + dependencies:
4940 + esrecurse: 4.3.0
4941 + estraverse: 5.3.0
4942 +
4943 + eslint-visitor-keys@3.4.3: {}
4944 +
4945 + eslint-visitor-keys@4.2.1: {}
4946 +
4947 + eslint-visitor-keys@5.0.1: {}
4948 +
4949 + eslint@9.39.5(jiti@2.7.0):
4950 + dependencies:
4951 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
4952 + '@eslint-community/regexpp': 4.12.2
4953 + '@eslint/config-array': 0.21.2
4954 + '@eslint/config-helpers': 0.4.2
4955 + '@eslint/core': 0.17.0
4956 + '@eslint/eslintrc': 3.3.7
4957 + '@eslint/js': 9.39.5
4958 + '@eslint/plugin-kit': 0.4.1
4959 + '@humanfs/node': 0.16.8
4960 + '@humanwhocodes/module-importer': 1.0.1
4961 + '@humanwhocodes/retry': 0.4.3
4962 + '@types/estree': 1.0.9
4963 + ajv: 6.15.0
4964 + chalk: 4.1.2
4965 + cross-spawn: 7.0.6
4966 + debug: 4.4.3
4967 + escape-string-regexp: 4.0.0
4968 + eslint-scope: 8.4.0
4969 + eslint-visitor-keys: 4.2.1
4970 + espree: 10.4.0
4971 + esquery: 1.7.0
4972 + esutils: 2.0.3
4973 + fast-deep-equal: 3.1.3
4974 + file-entry-cache: 8.0.0
4975 + find-up: 5.0.0
4976 + glob-parent: 6.0.2
4977 + ignore: 5.3.2
4978 + imurmurhash: 0.1.4
4979 + is-glob: 4.0.3
4980 + json-stable-stringify-without-jsonify: 1.0.1
4981 + lodash.merge: 4.6.2
4982 + minimatch: 3.1.5
4983 + natural-compare: 1.4.0
4984 + optionator: 0.9.4
4985 + optionalDependencies:
4986 + jiti: 2.7.0
4987 + transitivePeerDependencies:
4988 + - supports-color
4989 +
4990 + espree@10.4.0:
4991 + dependencies:
4992 + acorn: 8.18.0
4993 + acorn-jsx: 5.3.2(acorn@8.18.0)
4994 + eslint-visitor-keys: 4.2.1
4995 +
4996 + esquery@1.7.0:
4997 + dependencies:
4998 + estraverse: 5.3.0
4999 +
5000 + esrecurse@4.3.0:
5001 + dependencies:
5002 + estraverse: 5.3.0
5003 +
5004 + estraverse@5.3.0: {}
5005 +
5006 + estree-walker@3.0.3:
5007 + dependencies:
5008 + '@types/estree': 1.0.9
5009 +
5010 + esutils@2.0.3: {}
5011 +
5012 + expect-type@1.4.0: {}
5013 +
5014 + fast-deep-equal@3.1.3: {}
5015 +
5016 + fast-glob@3.3.1:
5017 + dependencies:
5018 + '@nodelib/fs.stat': 2.0.5
5019 + '@nodelib/fs.walk': 1.2.8
5020 + glob-parent: 5.1.2
5021 + merge2: 1.4.1
5022 + micromatch: 4.0.8
5023 +
5024 + fast-json-stable-stringify@2.1.0: {}
5025 +
5026 + fast-levenshtein@2.0.6: {}
5027 +
5028 + fastq@1.20.3:
5029 + dependencies:
5030 + reusify: 1.1.0
5031 +
5032 + fdir@6.5.0(picomatch@4.0.7):
5033 + optionalDependencies:
5034 + picomatch: 4.0.7
5035 +
5036 + file-entry-cache@8.0.0:
5037 + dependencies:
5038 + flat-cache: 4.0.1
5039 +
5040 + fill-range@7.1.1:
5041 + dependencies:
5042 + to-regex-range: 5.0.1
5043 +
5044 + find-up@5.0.0:
5045 + dependencies:
5046 + locate-path: 6.0.0
5047 + path-exists: 4.0.0
5048 +
5049 + flat-cache@4.0.1:
5050 + dependencies:
5051 + flatted: 3.4.4
5052 + keyv: 4.5.4
5053 +
5054 + flatted@3.4.4: {}
5055 +
5056 + for-each@0.3.5:
5057 + dependencies:
5058 + is-callable: 1.2.7
5059 +
5060 + fsevents@2.3.3:
5061 + optional: true
5062 +
5063 + function-bind@1.1.2: {}
5064 +
5065 + function.prototype.name@1.2.0:
5066 + dependencies:
5067 + call-bind: 1.0.9
5068 + call-bound: 1.0.4
5069 + es-define-property: 1.0.1
5070 + es-errors: 1.3.0
5071 + functions-have-names: 1.2.3
5072 + has-property-descriptors: 1.0.2
5073 + hasown: 2.0.4
5074 + is-callable: 1.2.7
5075 + is-document.all: 1.0.0
5076 +
5077 + functions-have-names@1.2.3: {}
5078 +
5079 + generator-function@2.0.1: {}
5080 +
5081 + gensync@1.0.0-beta.2: {}
5082 +
5083 + get-intrinsic@1.3.0:
5084 + dependencies:
5085 + call-bind-apply-helpers: 1.0.2
5086 + es-define-property: 1.0.1
5087 + es-errors: 1.3.0
5088 + es-object-atoms: 1.1.2
5089 + function-bind: 1.1.2
5090 + get-proto: 1.0.1
5091 + gopd: 1.2.0
5092 + has-symbols: 1.1.0
5093 + hasown: 2.0.4
5094 + math-intrinsics: 1.1.0
5095 +
5096 + get-proto@1.0.1:
5097 + dependencies:
5098 + dunder-proto: 1.0.1
5099 + es-object-atoms: 1.1.2
5100 +
5101 + get-symbol-description@1.1.0:
5102 + dependencies:
5103 + call-bound: 1.0.4
5104 + es-errors: 1.3.0
5105 + get-intrinsic: 1.3.0
5106 +
5107 + get-tsconfig@4.14.3:
5108 + dependencies:
5109 + resolve-pkg-maps: 1.0.0
5110 +
5111 + glob-parent@5.1.2:
5112 + dependencies:
5113 + is-glob: 4.0.3
5114 +
5115 + glob-parent@6.0.2:
5116 + dependencies:
5117 + is-glob: 4.0.3
5118 +
5119 + globals@14.0.0: {}
5120 +
5121 + globals@16.4.0: {}
5122 +
5123 + globalthis@1.0.4:
5124 + dependencies:
5125 + define-properties: 1.2.1
5126 + gopd: 1.2.0
5127 +
5128 + gopd@1.2.0: {}
5129 +
5130 + graceful-fs@4.2.11: {}
5131 +
5132 + has-bigints@1.1.0: {}
5133 +
5134 + has-flag@4.0.0: {}
5135 +
5136 + has-property-descriptors@1.0.2:
5137 + dependencies:
5138 + es-define-property: 1.0.1
5139 +
5140 + has-proto@1.2.0:
5141 + dependencies:
5142 + dunder-proto: 1.0.1
5143 +
5144 + has-symbols@1.1.0: {}
5145 +
5146 + has-tostringtag@1.0.2:
5147 + dependencies:
5148 + has-symbols: 1.1.0
5149 +
5150 + hasown@2.0.4:
5151 + dependencies:
5152 + function-bind: 1.1.2
5153 +
5154 + hermes-estree@0.25.1: {}
5155 +
5156 + hermes-parser@0.25.1:
5157 + dependencies:
5158 + hermes-estree: 0.25.1
5159 +
5160 + htmlparser2@10.1.0:
5161 + dependencies:
5162 + domelementtype: 2.3.0
5163 + domhandler: 5.0.3
5164 + domutils: 3.2.2
5165 + entities: 7.0.1
5166 +
5167 + iconv-lite@0.6.3:
5168 + dependencies:
5169 + safer-buffer: 2.1.2
5170 +
5171 + ignore@5.3.2: {}
5172 +
5173 + ignore@7.0.8: {}
5174 +
5175 + import-fresh@3.3.1:
5176 + dependencies:
5177 + parent-module: 1.0.1
5178 + resolve-from: 4.0.0
5179 +
5180 + imurmurhash@0.1.4: {}
5181 +
5182 + internal-slot@1.1.0:
5183 + dependencies:
5184 + es-errors: 1.3.0
5185 + hasown: 2.0.4
5186 + side-channel: 1.1.1
5187 +
5188 + is-array-buffer@3.0.5:
5189 + dependencies:
5190 + call-bind: 1.0.9
5191 + call-bound: 1.0.4
5192 + get-intrinsic: 1.3.0
5193 +
5194 + is-async-function@2.1.1:
5195 + dependencies:
5196 + async-function: 1.0.0
5197 + call-bound: 1.0.4
5198 + get-proto: 1.0.1
5199 + has-tostringtag: 1.0.2
5200 + safe-regex-test: 1.1.0
5201 +
5202 + is-bigint@1.1.0:
5203 + dependencies:
5204 + has-bigints: 1.1.0
5205 +
5206 + is-boolean-object@1.2.2:
5207 + dependencies:
5208 + call-bound: 1.0.4
5209 + has-tostringtag: 1.0.2
5210 +
5211 + is-bun-module@2.0.0:
5212 + dependencies:
5213 + semver: 7.8.5
5214 +
5215 + is-callable@1.2.7: {}
5216 +
5217 + is-core-module@2.16.2:
5218 + dependencies:
5219 + hasown: 2.0.4
5220 +
5221 + is-data-view@1.0.2:
5222 + dependencies:
5223 + call-bound: 1.0.4
5224 + get-intrinsic: 1.3.0
5225 + is-typed-array: 1.1.15
5226 +
5227 + is-date-object@1.1.0:
5228 + dependencies:
5229 + call-bound: 1.0.4
5230 + has-tostringtag: 1.0.2
5231 +
5232 + is-document.all@1.0.0:
5233 + dependencies:
5234 + call-bound: 1.0.4
5235 +
5236 + is-extglob@2.1.1: {}
5237 +
5238 + is-finalizationregistry@1.1.1:
5239 + dependencies:
5240 + call-bound: 1.0.4
5241 +
5242 + is-generator-function@1.1.2:
5243 + dependencies:
5244 + call-bound: 1.0.4
5245 + generator-function: 2.0.1
5246 + get-proto: 1.0.1
5247 + has-tostringtag: 1.0.2
5248 + safe-regex-test: 1.1.0
5249 +
5250 + is-glob@4.0.3:
5251 + dependencies:
5252 + is-extglob: 2.1.1
5253 +
5254 + is-map@2.0.3: {}
5255 +
5256 + is-negative-zero@2.0.3: {}
5257 +
5258 + is-number-object@1.1.1:
5259 + dependencies:
5260 + call-bound: 1.0.4
5261 + has-tostringtag: 1.0.2
5262 +
5263 + is-number@7.0.0: {}
5264 +
5265 + is-regex@1.2.1:
5266 + dependencies:
5267 + call-bound: 1.0.4
5268 + gopd: 1.2.0
5269 + has-tostringtag: 1.0.2
5270 + hasown: 2.0.4
5271 +
5272 + is-set@2.0.3: {}
5273 +
5274 + is-shared-array-buffer@1.0.4:
5275 + dependencies:
5276 + call-bound: 1.0.4
5277 +
5278 + is-string@1.1.1:
5279 + dependencies:
5280 + call-bound: 1.0.4
5281 + has-tostringtag: 1.0.2
5282 +
5283 + is-symbol@1.1.1:
5284 + dependencies:
5285 + call-bound: 1.0.4
5286 + has-symbols: 1.1.0
5287 + safe-regex-test: 1.1.0
5288 +
5289 + is-typed-array@1.1.15:
5290 + dependencies:
5291 + which-typed-array: 1.1.22
5292 +
5293 + is-weakmap@2.0.2: {}
5294 +
5295 + is-weakref@1.1.1:
5296 + dependencies:
5297 + call-bound: 1.0.4
5298 +
5299 + is-weakset@2.0.4:
5300 + dependencies:
5301 + call-bound: 1.0.4
5302 + get-intrinsic: 1.3.0
5303 +
5304 + isarray@2.0.5: {}
5305 +
5306 + isexe@2.0.0: {}
5307 +
5308 + iterator.prototype@1.1.5:
5309 + dependencies:
5310 + define-data-property: 1.1.4
5311 + es-object-atoms: 1.1.2
5312 + get-intrinsic: 1.3.0
5313 + get-proto: 1.0.1
5314 + has-symbols: 1.1.0
5315 + set-function-name: 2.0.2
5316 +
5317 + jiti@2.7.0: {}
5318 +
5319 + js-tokens@4.0.0: {}
5320 +
5321 + js-tokens@9.0.1: {}
5322 +
5323 + js-yaml@4.3.2:
5324 + dependencies:
5325 + argparse: 2.0.1
5326 +
5327 + jsesc@3.1.0: {}
5328 +
5329 + json-buffer@3.0.1: {}
5330 +
5331 + json-schema-traverse@0.4.1: {}
5332 +
5333 + json-stable-stringify-without-jsonify@1.0.1: {}
5334 +
5335 + json5@1.0.2:
5336 + dependencies:
5337 + minimist: 1.2.8
5338 +
5339 + json5@2.2.3: {}
5340 +
5341 + jsx-ast-utils@3.3.5:
5342 + dependencies:
5343 + array-includes: 3.1.9
5344 + array.prototype.flat: 1.3.3
5345 + object.assign: 4.1.7
5346 + object.values: 1.2.1
5347 +
5348 + keyv@4.5.4:
5349 + dependencies:
5350 + json-buffer: 3.0.1
5351 +
5352 + language-subtag-registry@0.3.23: {}
5353 +
5354 + language-tags@1.0.9:
5355 + dependencies:
5356 + language-subtag-registry: 0.3.23
5357 +
5358 + levn@0.4.1:
5359 + dependencies:
5360 + prelude-ls: 1.2.1
5361 + type-check: 0.4.0
5362 +
5363 + lightningcss-android-arm64@1.32.0:
5364 + optional: true
5365 +
5366 + lightningcss-darwin-arm64@1.32.0:
5367 + optional: true
5368 +
5369 + lightningcss-darwin-x64@1.32.0:
5370 + optional: true
5371 +
5372 + lightningcss-freebsd-x64@1.32.0:
5373 + optional: true
5374 +
5375 + lightningcss-linux-arm-gnueabihf@1.32.0:
5376 + optional: true
5377 +
5378 + lightningcss-linux-arm64-gnu@1.32.0:
5379 + optional: true
5380 +
5381 + lightningcss-linux-arm64-musl@1.32.0:
5382 + optional: true
5383 +
5384 + lightningcss-linux-x64-gnu@1.32.0:
5385 + optional: true
5386 +
5387 + lightningcss-linux-x64-musl@1.32.0:
5388 + optional: true
5389 +
5390 + lightningcss-win32-arm64-msvc@1.32.0:
5391 + optional: true
5392 +
5393 + lightningcss-win32-x64-msvc@1.32.0:
5394 + optional: true
5395 +
5396 + lightningcss@1.32.0:
5397 + dependencies:
5398 + detect-libc: 2.1.2
5399 + optionalDependencies:
5400 + lightningcss-android-arm64: 1.32.0
5401 + lightningcss-darwin-arm64: 1.32.0
5402 + lightningcss-darwin-x64: 1.32.0
5403 + lightningcss-freebsd-x64: 1.32.0
5404 + lightningcss-linux-arm-gnueabihf: 1.32.0
5405 + lightningcss-linux-arm64-gnu: 1.32.0
5406 + lightningcss-linux-arm64-musl: 1.32.0
5407 + lightningcss-linux-x64-gnu: 1.32.0
5408 + lightningcss-linux-x64-musl: 1.32.0
5409 + lightningcss-win32-arm64-msvc: 1.32.0
5410 + lightningcss-win32-x64-msvc: 1.32.0
5411 +
5412 + locate-path@6.0.0:
5413 + dependencies:
5414 + p-locate: 5.0.0
5415 +
5416 + lodash.merge@4.6.2: {}
5417 +
5418 + loose-envify@1.4.0:
5419 + dependencies:
5420 + js-tokens: 4.0.0
5421 +
5422 + loupe@3.2.1: {}
5423 +
5424 + lru-cache@5.1.1:
5425 + dependencies:
5426 + yallist: 3.1.1
5427 +
5428 + lucide-react@1.41.0(react@19.2.8):
5429 + dependencies:
5430 + react: 19.2.8
5431 +
5432 + magic-string@0.30.21:
5433 + dependencies:
5434 + '@jridgewell/sourcemap-codec': 1.6.0
5435 +
5436 + math-intrinsics@1.1.0: {}
5437 +
5438 + merge2@1.4.1: {}
5439 +
5440 + micromatch@4.0.8:
5441 + dependencies:
5442 + braces: 3.0.3
5443 + picomatch: 2.3.2
5444 +
5445 + minimatch@10.2.6:
5446 + dependencies:
5447 + brace-expansion: 5.0.9
5448 +
5449 + minimatch@3.1.5:
5450 + dependencies:
5451 + brace-expansion: 1.1.18
5452 +
5453 + minimist@1.2.8: {}
5454 +
5455 + ms@2.1.3: {}
5456 +
5457 + nanoid@3.3.18: {}
5458 +
5459 + napi-postinstall@0.3.4: {}
5460 +
5461 + natural-compare@1.4.0: {}
5462 +
5463 + next@16.3.4(@babel/core@7.29.7)(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
5464 + dependencies:
5465 + '@next/env': 16.3.4
5466 + '@swc/helpers': 0.5.23
5467 + baseline-browser-mapping: 2.11.21
5468 + caniuse-lite: 1.0.30001810
5469 + postcss: 8.5.23
5470 + react: 19.2.8
5471 + react-dom: 19.2.8(react@19.2.8)
5472 + styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.8)
5473 + optionalDependencies:
5474 + '@next/swc-darwin-arm64': 16.3.4
5475 + '@next/swc-darwin-x64': 16.3.4
5476 + '@next/swc-linux-arm64-gnu': 16.3.4
5477 + '@next/swc-linux-arm64-musl': 16.3.4
5478 + '@next/swc-linux-x64-gnu': 16.3.4
5479 + '@next/swc-linux-x64-musl': 16.3.4
5480 + '@next/swc-win32-arm64-msvc': 16.3.4
5481 + '@next/swc-win32-x64-msvc': 16.3.4
5482 + sharp: 0.35.4(@types/node@24.13.3)
5483 + transitivePeerDependencies:
5484 + - '@babel/core'
5485 + - '@types/node'
5486 + - babel-plugin-macros
5487 +
5488 + node-exports-info@1.6.2:
5489 + dependencies:
5490 + array.prototype.flatmap: 1.3.3
5491 + es-errors: 1.3.0
5492 + object.entries: 1.1.9
5493 + semver: 6.3.1
5494 +
5495 + node-releases@2.0.54: {}
5496 +
5497 + nth-check@2.1.1:
5498 + dependencies:
5499 + boolbase: 1.0.0
5500 +
5501 + object-assign@4.1.1: {}
5502 +
5503 + object-inspect@1.13.4: {}
5504 +
5505 + object-keys@1.1.1: {}
5506 +
5507 + object.assign@4.1.7:
5508 + dependencies:
5509 + call-bind: 1.0.9
5510 + call-bound: 1.0.4
5511 + define-properties: 1.2.1
5512 + es-object-atoms: 1.1.2
5513 + has-symbols: 1.1.0
5514 + object-keys: 1.1.1
5515 +
5516 + object.entries@1.1.9:
5517 + dependencies:
5518 + call-bind: 1.0.9
5519 + call-bound: 1.0.4
5520 + define-properties: 1.2.1
5521 + es-object-atoms: 1.1.2
5522 +
5523 + object.fromentries@2.0.8:
5524 + dependencies:
5525 + call-bind: 1.0.9
5526 + define-properties: 1.2.1
5527 + es-abstract: 1.24.2
5528 + es-object-atoms: 1.1.2
5529 +
5530 + object.groupby@1.0.3:
5531 + dependencies:
5532 + call-bind: 1.0.9
5533 + define-properties: 1.2.1
5534 + es-abstract: 1.24.2
5535 +
5536 + object.values@1.2.1:
5537 + dependencies:
5538 + call-bind: 1.0.9
5539 + call-bound: 1.0.4
5540 + define-properties: 1.2.1
5541 + es-object-atoms: 1.1.2
5542 +
5543 + on-exit-leak-free@2.1.2: {}
5544 +
5545 + optionator@0.9.4:
5546 + dependencies:
5547 + deep-is: 0.1.4
5548 + fast-levenshtein: 2.0.6
5549 + levn: 0.4.1
5550 + prelude-ls: 1.2.1
5551 + type-check: 0.4.0
5552 + word-wrap: 1.2.5
5553 +
5554 + own-keys@1.0.2:
5555 + dependencies:
5556 + call-bound: 1.0.4
5557 + get-intrinsic: 1.3.0
5558 + object-keys: 1.1.1
5559 + safe-push-apply: 1.0.0
5560 +
5561 + p-limit@3.1.0:
5562 + dependencies:
5563 + yocto-queue: 0.1.0
5564 +
5565 + p-locate@5.0.0:
5566 + dependencies:
5567 + p-limit: 3.1.0
5568 +
5569 + parent-module@1.0.1:
5570 + dependencies:
5571 + callsites: 3.1.0
5572 +
5573 + parse5-htmlparser2-tree-adapter@7.1.0:
5574 + dependencies:
5575 + domhandler: 5.0.3
5576 + parse5: 7.3.0
5577 +
5578 + parse5-parser-stream@7.1.2:
5579 + dependencies:
5580 + parse5: 7.3.0
5581 +
5582 + parse5@7.3.0:
5583 + dependencies:
5584 + entities: 6.0.1
5585 +
5586 + path-exists@4.0.0: {}
5587 +
5588 + path-key@3.1.1: {}
5589 +
5590 + path-parse@1.0.7: {}
5591 +
5592 + pathe@2.0.3: {}
5593 +
5594 + pathval@2.0.1: {}
5595 +
5596 + picocolors@1.1.1: {}
5597 +
5598 + picomatch@2.3.2: {}
5599 +
5600 + picomatch@4.0.7: {}
5601 +
5602 + pino-abstract-transport@2.0.0:
5603 + dependencies:
5604 + split2: 4.2.0
5605 +
5606 + pino-std-serializers@7.1.0: {}
5607 +
5608 + pino@9.14.0:
5609 + dependencies:
5610 + '@pinojs/redact': 0.4.0
5611 + atomic-sleep: 1.0.0
5612 + on-exit-leak-free: 2.1.2
5613 + pino-abstract-transport: 2.0.0
5614 + pino-std-serializers: 7.1.0
5615 + process-warning: 5.1.0
5616 + quick-format-unescaped: 4.0.4
5617 + real-require: 0.2.0
5618 + safe-stable-stringify: 2.5.0
5619 + sonic-boom: 4.2.1
5620 + thread-stream: 3.2.0
5621 +
5622 + possible-typed-array-names@1.1.0: {}
5623 +
5624 + postcss@8.5.23:
5625 + dependencies:
5626 + nanoid: 3.3.18
5627 + picocolors: 1.1.1
5628 + source-map-js: 1.2.1
5629 +
5630 + postcss@8.5.28:
5631 + dependencies:
5632 + nanoid: 3.3.18
5633 + picocolors: 1.1.1
5634 + source-map-js: 1.2.1
5635 +
5636 + postgres@3.4.9: {}
5637 +
5638 + prelude-ls@1.2.1: {}
5639 +
5640 + process-warning@5.1.0: {}
5641 +
5642 + prop-types@15.8.1:
5643 + dependencies:
5644 + loose-envify: 1.4.0
5645 + object-assign: 4.1.1
5646 + react-is: 16.13.1
5647 +
5648 + punycode@2.3.1: {}
5649 +
5650 + queue-microtask@1.2.3: {}
5651 +
5652 + quick-format-unescaped@4.0.4: {}
5653 +
5654 + react-dom@19.2.8(react@19.2.8):
5655 + dependencies:
5656 + react: 19.2.8
5657 + scheduler: 0.27.0
5658 +
5659 + react-is@16.13.1: {}
5660 +
5661 + react@19.2.8: {}
5662 +
5663 + real-require@0.2.0: {}
5664 +
5665 + reflect.getprototypeof@1.0.10:
5666 + dependencies:
5667 + call-bind: 1.0.9
5668 + define-properties: 1.2.1
5669 + es-abstract: 1.24.2
5670 + es-errors: 1.3.0
5671 + es-object-atoms: 1.1.2
5672 + get-intrinsic: 1.3.0
5673 + get-proto: 1.0.1
5674 + which-builtin-type: 1.2.1
5675 +
5676 + regexp.prototype.flags@1.5.4:
5677 + dependencies:
5678 + call-bind: 1.0.9
5679 + define-properties: 1.2.1
5680 + es-errors: 1.3.0
5681 + get-proto: 1.0.1
5682 + gopd: 1.2.0
5683 + set-function-name: 2.0.2
5684 +
5685 + resolve-from@4.0.0: {}
5686 +
5687 + resolve-pkg-maps@1.0.0: {}
5688 +
5689 + resolve@2.0.0-next.7:
5690 + dependencies:
5691 + es-errors: 1.3.0
5692 + is-core-module: 2.16.2
5693 + node-exports-info: 1.6.2
5694 + object-keys: 1.1.1
5695 + path-parse: 1.0.7
5696 + supports-preserve-symlinks-flag: 1.0.0
5697 +
5698 + reusify@1.1.0: {}
5699 +
5700 + rollup@4.63.1:
5701 + dependencies:
5702 + '@types/estree': 1.0.9
5703 + optionalDependencies:
5704 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1
5705 + '@rollup/rollup-android-arm-eabi': 4.63.1
5706 + '@rollup/rollup-android-arm64': 4.63.1
5707 + '@rollup/rollup-darwin-arm64': 4.63.1
5708 + '@rollup/rollup-darwin-x64': 4.63.1
5709 + '@rollup/rollup-freebsd-arm64': 4.63.1
5710 + '@rollup/rollup-freebsd-x64': 4.63.1
5711 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.1
5712 + '@rollup/rollup-linux-arm-musleabihf': 4.63.1
5713 + '@rollup/rollup-linux-arm64-gnu': 4.63.1
5714 + '@rollup/rollup-linux-arm64-musl': 4.63.1
5715 + '@rollup/rollup-linux-loong64-gnu': 4.63.1
5716 + '@rollup/rollup-linux-loong64-musl': 4.63.1
5717 + '@rollup/rollup-linux-ppc64-gnu': 4.63.1
5718 + '@rollup/rollup-linux-ppc64-musl': 4.63.1
5719 + '@rollup/rollup-linux-riscv64-gnu': 4.63.1
5720 + '@rollup/rollup-linux-riscv64-musl': 4.63.1
5721 + '@rollup/rollup-linux-s390x-gnu': 4.63.1
5722 + '@rollup/rollup-linux-x64-gnu': 4.63.1
5723 + '@rollup/rollup-linux-x64-musl': 4.63.1
5724 + '@rollup/rollup-openbsd-x64': 4.63.1
5725 + '@rollup/rollup-openharmony-arm64': 4.63.1
5726 + '@rollup/rollup-win32-arm64-msvc': 4.63.1
5727 + '@rollup/rollup-win32-ia32-msvc': 4.63.1
5728 + '@rollup/rollup-win32-x64-gnu': 4.63.1
5729 + '@rollup/rollup-win32-x64-msvc': 4.63.1
5730 + fsevents: 2.3.3
5731 +
5732 + run-parallel@1.2.0:
5733 + dependencies:
5734 + queue-microtask: 1.2.3
5735 +
5736 + safe-array-concat@1.1.4:
5737 + dependencies:
5738 + call-bind: 1.0.9
5739 + call-bound: 1.0.4
5740 + get-intrinsic: 1.3.0
5741 + has-symbols: 1.1.0
5742 + isarray: 2.0.5
5743 +
5744 + safe-push-apply@1.0.0:
5745 + dependencies:
5746 + es-errors: 1.3.0
5747 + isarray: 2.0.5
5748 +
5749 + safe-regex-test@1.1.0:
5750 + dependencies:
5751 + call-bound: 1.0.4
5752 + es-errors: 1.3.0
5753 + is-regex: 1.2.1
5754 +
5755 + safe-stable-stringify@2.5.0: {}
5756 +
5757 + safer-buffer@2.1.2: {}
5758 +
5759 + scheduler@0.27.0: {}
5760 +
5761 + semver@6.3.1: {}
5762 +
5763 + semver@7.8.5: {}
5764 +
5765 + server-only@0.0.1: {}
5766 +
5767 + set-function-length@1.2.2:
5768 + dependencies:
5769 + define-data-property: 1.1.4
5770 + es-errors: 1.3.0
5771 + function-bind: 1.1.2
5772 + get-intrinsic: 1.3.0
5773 + gopd: 1.2.0
5774 + has-property-descriptors: 1.0.2
5775 +
5776 + set-function-name@2.0.2:
5777 + dependencies:
5778 + define-data-property: 1.1.4
5779 + es-errors: 1.3.0
5780 + functions-have-names: 1.2.3
5781 + has-property-descriptors: 1.0.2
5782 +
5783 + set-proto@1.0.0:
5784 + dependencies:
5785 + dunder-proto: 1.0.1
5786 + es-errors: 1.3.0
5787 + es-object-atoms: 1.1.2
5788 +
5789 + sharp@0.35.4(@types/node@24.13.3):
5790 + dependencies:
5791 + '@img/colour': 1.1.0
5792 + detect-libc: 2.1.2
5793 + semver: 7.8.5
5794 + optionalDependencies:
5795 + '@img/sharp-darwin-arm64': 0.35.4
5796 + '@img/sharp-darwin-x64': 0.35.4
5797 + '@img/sharp-freebsd-wasm32': 0.35.4
5798 + '@img/sharp-libvips-darwin-arm64': 1.3.3
5799 + '@img/sharp-libvips-darwin-x64': 1.3.3
5800 + '@img/sharp-libvips-linux-arm': 1.3.3
5801 + '@img/sharp-libvips-linux-arm64': 1.3.3
5802 + '@img/sharp-libvips-linux-ppc64': 1.3.3
5803 + '@img/sharp-libvips-linux-riscv64': 1.3.3
5804 + '@img/sharp-libvips-linux-s390x': 1.3.3
5805 + '@img/sharp-libvips-linux-x64': 1.3.3
5806 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
5807 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3
5808 + '@img/sharp-linux-arm': 0.35.4
5809 + '@img/sharp-linux-arm64': 0.35.4
5810 + '@img/sharp-linux-ppc64': 0.35.4
5811 + '@img/sharp-linux-riscv64': 0.35.4
5812 + '@img/sharp-linux-s390x': 0.35.4
5813 + '@img/sharp-linux-x64': 0.35.4
5814 + '@img/sharp-linuxmusl-arm64': 0.35.4
5815 + '@img/sharp-linuxmusl-x64': 0.35.4
5816 + '@img/sharp-webcontainers-wasm32': 0.35.4
5817 + '@img/sharp-win32-arm64': 0.35.4
5818 + '@img/sharp-win32-ia32': 0.35.4
5819 + '@img/sharp-win32-x64': 0.35.4
5820 + '@types/node': 24.13.3
5821 + optional: true
5822 +
5823 + shebang-command@2.0.0:
5824 + dependencies:
5825 + shebang-regex: 3.0.0
5826 +
5827 + shebang-regex@3.0.0: {}
5828 +
5829 + side-channel-list@1.0.1:
5830 + dependencies:
5831 + es-errors: 1.3.0
5832 + object-inspect: 1.13.4
5833 +
5834 + side-channel-map@1.0.1:
5835 + dependencies:
5836 + call-bound: 1.0.4
5837 + es-errors: 1.3.0
5838 + get-intrinsic: 1.3.0
5839 + object-inspect: 1.13.4
5840 +
5841 + side-channel-weakmap@1.0.2:
5842 + dependencies:
5843 + call-bound: 1.0.4
5844 + es-errors: 1.3.0
5845 + get-intrinsic: 1.3.0
5846 + object-inspect: 1.13.4
5847 + side-channel-map: 1.0.1
5848 +
5849 + side-channel@1.1.1:
5850 + dependencies:
5851 + es-errors: 1.3.0
5852 + object-inspect: 1.13.4
5853 + side-channel-list: 1.0.1
5854 + side-channel-map: 1.0.1
5855 + side-channel-weakmap: 1.0.2
5856 +
5857 + siginfo@2.0.0: {}
5858 +
5859 + sonic-boom@4.2.1:
5860 + dependencies:
5861 + atomic-sleep: 1.0.0
5862 +
5863 + source-map-js@1.2.1: {}
5864 +
5865 + source-map-support@0.5.21:
5866 + dependencies:
5867 + buffer-from: 1.1.2
5868 + source-map: 0.6.1
5869 +
5870 + source-map@0.6.1: {}
5871 +
5872 + split2@4.2.0: {}
5873 +
5874 + stable-hash@0.0.5: {}
5875 +
5876 + stackback@0.0.2: {}
5877 +
5878 + std-env@3.10.0: {}
5879 +
5880 + stop-iteration-iterator@1.1.0:
5881 + dependencies:
5882 + es-errors: 1.3.0
5883 + internal-slot: 1.1.0
5884 +
5885 + string.prototype.includes@2.0.1:
5886 + dependencies:
5887 + call-bind: 1.0.9
5888 + define-properties: 1.2.1
5889 + es-abstract: 1.24.2
5890 +
5891 + string.prototype.matchall@4.1.0:
5892 + dependencies:
5893 + call-bind: 1.0.9
5894 + call-bound: 1.0.4
5895 + define-properties: 1.2.1
5896 + es-abstract: 1.24.2
5897 + es-errors: 1.3.0
5898 + es-object-atoms: 1.1.2
5899 + get-intrinsic: 1.3.0
5900 + gopd: 1.2.0
5901 + has-symbols: 1.1.0
5902 + internal-slot: 1.1.0
5903 + regexp.prototype.flags: 1.5.4
5904 + set-function-name: 2.0.2
5905 + side-channel: 1.1.1
5906 +
5907 + string.prototype.repeat@1.0.0:
5908 + dependencies:
5909 + define-properties: 1.2.1
5910 + es-abstract: 1.24.2
5911 +
5912 + string.prototype.trim@1.2.11:
5913 + dependencies:
5914 + call-bind: 1.0.9
5915 + call-bound: 1.0.4
5916 + define-data-property: 1.1.4
5917 + define-properties: 1.2.1
5918 + es-abstract: 1.24.2
5919 + es-object-atoms: 1.1.2
5920 + has-property-descriptors: 1.0.2
5921 + safe-regex-test: 1.1.0
5922 +
5923 + string.prototype.trimend@1.0.10:
5924 + dependencies:
5925 + call-bind: 1.0.9
5926 + call-bound: 1.0.4
5927 + define-properties: 1.2.1
5928 + es-object-atoms: 1.1.2
5929 +
5930 + string.prototype.trimstart@1.0.8:
5931 + dependencies:
5932 + call-bind: 1.0.9
5933 + define-properties: 1.2.1
5934 + es-object-atoms: 1.1.2
5935 +
5936 + strip-bom@3.0.0: {}
5937 +
5938 + strip-json-comments@3.1.1: {}
5939 +
5940 + strip-literal@3.1.0:
5941 + dependencies:
5942 + js-tokens: 9.0.1
5943 +
5944 + styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.8):
5945 + dependencies:
5946 + client-only: 0.0.1
5947 + react: 19.2.8
5948 + optionalDependencies:
5949 + '@babel/core': 7.29.7
5950 +
5951 + supports-color@7.2.0:
5952 + dependencies:
5953 + has-flag: 4.0.0
5954 +
5955 + supports-preserve-symlinks-flag@1.0.0: {}
5956 +
5957 + tailwindcss@4.3.3: {}
5958 +
5959 + tapable@2.3.3: {}
5960 +
5961 + thread-stream@3.2.0:
5962 + dependencies:
5963 + real-require: 0.2.0
5964 +
5965 + tinybench@2.9.0: {}
5966 +
5967 + tinyexec@0.3.2: {}
5968 +
5969 + tinyglobby@0.2.17:
5970 + dependencies:
5971 + fdir: 6.5.0(picomatch@4.0.7)
5972 + picomatch: 4.0.7
5973 +
5974 + tinypool@1.1.1: {}
5975 +
5976 + tinyrainbow@2.0.0: {}
5977 +
5978 + tinyspy@4.0.6: {}
5979 +
5980 + to-regex-range@5.0.1:
5981 + dependencies:
5982 + is-number: 7.0.0
5983 +
5984 + ts-api-utils@2.5.0(typescript@5.9.3):
5985 + dependencies:
5986 + typescript: 5.9.3
5987 +
5988 + tsconfig-paths@3.15.0:
5989 + dependencies:
5990 + '@types/json5': 0.0.29
5991 + json5: 1.0.2
5992 + minimist: 1.2.8
5993 + strip-bom: 3.0.0
5994 +
5995 + tslib@2.8.1: {}
5996 +
5997 + tsx@4.23.13:
5998 + dependencies:
5999 + esbuild: 0.28.2
6000 + optionalDependencies:
6001 + fsevents: 2.3.3
6002 +
6003 + type-check@0.4.0:
6004 + dependencies:
6005 + prelude-ls: 1.2.1
6006 +
6007 + typed-array-buffer@1.0.3:
6008 + dependencies:
6009 + call-bound: 1.0.4
6010 + es-errors: 1.3.0
6011 + is-typed-array: 1.1.15
6012 +
6013 + typed-array-byte-length@1.0.3:
6014 + dependencies:
6015 + call-bind: 1.0.9
6016 + for-each: 0.3.5
6017 + gopd: 1.2.0
6018 + has-proto: 1.2.0
6019 + is-typed-array: 1.1.15
6020 +
6021 + typed-array-byte-offset@1.0.4:
6022 + dependencies:
6023 + available-typed-arrays: 1.0.7
6024 + call-bind: 1.0.9
6025 + for-each: 0.3.5
6026 + gopd: 1.2.0
6027 + has-proto: 1.2.0
6028 + is-typed-array: 1.1.15
6029 + reflect.getprototypeof: 1.0.10
6030 +
6031 + typed-array-length@1.0.8:
6032 + dependencies:
6033 + call-bind: 1.0.9
6034 + for-each: 0.3.5
6035 + gopd: 1.2.0
6036 + is-typed-array: 1.1.15
6037 + possible-typed-array-names: 1.1.0
6038 + reflect.getprototypeof: 1.0.10
6039 +
6040 + typescript-eslint@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3):
6041 + dependencies:
6042 + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
6043 + '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
6044 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3)
6045 + '@typescript-eslint/utils': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
6046 + eslint: 9.39.5(jiti@2.7.0)
6047 + typescript: 5.9.3
6048 + transitivePeerDependencies:
6049 + - supports-color
6050 +
6051 + typescript@5.9.3: {}
6052 +
6053 + unbox-primitive@1.1.0:
6054 + dependencies:
6055 + call-bound: 1.0.4
6056 + has-bigints: 1.1.0
6057 + has-symbols: 1.1.0
6058 + which-boxed-primitive: 1.1.1
6059 +
6060 + undici-types@7.18.2: {}
6061 +
6062 + undici@7.29.1: {}
6063 +
6064 + unrs-resolver@1.12.2:
6065 + dependencies:
6066 + napi-postinstall: 0.3.4
6067 + optionalDependencies:
6068 + '@unrs/resolver-binding-android-arm-eabi': 1.12.2
6069 + '@unrs/resolver-binding-android-arm64': 1.12.2
6070 + '@unrs/resolver-binding-darwin-arm64': 1.12.2
6071 + '@unrs/resolver-binding-darwin-x64': 1.12.2
6072 + '@unrs/resolver-binding-freebsd-x64': 1.12.2
6073 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2
6074 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2
6075 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2
6076 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2
6077 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2
6078 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2
6079 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2
6080 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2
6081 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2
6082 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2
6083 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2
6084 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2
6085 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2
6086 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2
6087 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2
6088 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2
6089 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2
6090 +
6091 + update-browserslist-db@1.3.2(browserslist@4.28.9):
6092 + dependencies:
6093 + browserslist: 4.28.9
6094 + escalade: 3.2.0
6095 + picocolors: 1.1.1
6096 +
6097 + uri-js@4.4.1:
6098 + dependencies:
6099 + punycode: 2.3.1
6100 +
6101 + vite-node@3.2.4(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13):
6102 + dependencies:
6103 + cac: 6.7.14
6104 + debug: 4.4.3
6105 + es-module-lexer: 1.7.0
6106 + pathe: 2.0.3
6107 + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
6108 + transitivePeerDependencies:
6109 + - '@types/node'
6110 + - jiti
6111 + - less
6112 + - lightningcss
6113 + - sass
6114 + - sass-embedded
6115 + - stylus
6116 + - sugarss
6117 + - supports-color
6118 + - terser
6119 + - tsx
6120 + - yaml
6121 +
6122 + vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13):
6123 + dependencies:
6124 + esbuild: 0.28.2
6125 + fdir: 6.5.0(picomatch@4.0.7)
6126 + picomatch: 4.0.7
6127 + postcss: 8.5.28
6128 + rollup: 4.63.1
6129 + tinyglobby: 0.2.17
6130 + optionalDependencies:
6131 + '@types/node': 24.13.3
6132 + fsevents: 2.3.3
6133 + jiti: 2.7.0
6134 + lightningcss: 1.32.0
6135 + tsx: 4.23.13
6136 +
6137 + vitest@3.2.7(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13):
6138 + dependencies:
6139 + '@types/chai': 5.2.3
6140 + '@vitest/expect': 3.2.7
6141 + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13))
6142 + '@vitest/pretty-format': 3.2.7
6143 + '@vitest/runner': 3.2.7
6144 + '@vitest/snapshot': 3.2.7
6145 + '@vitest/spy': 3.2.7
6146 + '@vitest/utils': 3.2.7
6147 + chai: 5.3.3
6148 + debug: 4.4.3
6149 + expect-type: 1.4.0
6150 + magic-string: 0.30.21
6151 + pathe: 2.0.3
6152 + picomatch: 4.0.7
6153 + std-env: 3.10.0
6154 + tinybench: 2.9.0
6155 + tinyexec: 0.3.2
6156 + tinyglobby: 0.2.17
6157 + tinypool: 1.1.1
6158 + tinyrainbow: 2.0.0
6159 + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
6160 + vite-node: 3.2.4(@types/node@24.13.3)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.13)
6161 + why-is-node-running: 2.3.0
6162 + optionalDependencies:
6163 + '@types/node': 24.13.3
6164 + transitivePeerDependencies:
6165 + - jiti
6166 + - less
6167 + - lightningcss
6168 + - msw
6169 + - sass
6170 + - sass-embedded
6171 + - stylus
6172 + - sugarss
6173 + - supports-color
6174 + - terser
6175 + - tsx
6176 + - yaml
6177 +
6178 + whatwg-encoding@3.1.1:
6179 + dependencies:
6180 + iconv-lite: 0.6.3
6181 +
6182 + whatwg-mimetype@4.0.0: {}
6183 +
6184 + which-boxed-primitive@1.1.1:
6185 + dependencies:
6186 + is-bigint: 1.1.0
6187 + is-boolean-object: 1.2.2
6188 + is-number-object: 1.1.1
6189 + is-string: 1.1.1
6190 + is-symbol: 1.1.1
6191 +
6192 + which-builtin-type@1.2.1:
6193 + dependencies:
6194 + call-bound: 1.0.4
6195 + function.prototype.name: 1.2.0
6196 + has-tostringtag: 1.0.2
6197 + is-async-function: 2.1.1
6198 + is-date-object: 1.1.0
6199 + is-finalizationregistry: 1.1.1
6200 + is-generator-function: 1.1.2
6201 + is-regex: 1.2.1
6202 + is-weakref: 1.1.1
6203 + isarray: 2.0.5
6204 + which-boxed-primitive: 1.1.1
6205 + which-collection: 1.0.2
6206 + which-typed-array: 1.1.22
6207 +
6208 + which-collection@1.0.2:
6209 + dependencies:
6210 + is-map: 2.0.3
6211 + is-set: 2.0.3
6212 + is-weakmap: 2.0.2
6213 + is-weakset: 2.0.4
6214 +
6215 + which-typed-array@1.1.22:
6216 + dependencies:
6217 + available-typed-arrays: 1.0.7
6218 + call-bind: 1.0.9
6219 + call-bound: 1.0.4
6220 + for-each: 0.3.5
6221 + get-proto: 1.0.1
6222 + gopd: 1.2.0
6223 + has-tostringtag: 1.0.2
6224 +
6225 + which@2.0.2:
6226 + dependencies:
6227 + isexe: 2.0.0
6228 +
6229 + why-is-node-running@2.3.0:
6230 + dependencies:
6231 + siginfo: 2.0.0
6232 + stackback: 0.0.2
6233 +
6234 + word-wrap@1.2.5: {}
6235 +
6236 + yallist@3.1.1: {}
6237 +
6238 + yocto-queue@0.1.0: {}
6239 +
6240 + zod-validation-error@4.0.2(zod@4.5.4):
6241 + dependencies:
6242 + zod: 4.5.4
6243 +
6244 + zod@4.5.4: {}
added pnpm-workspace.yaml +5 −0
@@ -0,0 +1,5 @@
1 +packages:
2 + - apps/*
3 + - packages/*
4 + - workers
5 + - connectors
added scripts/build-registry.ts +33 −0
@@ -0,0 +1,33 @@
1 +/**
2 + * Assemble connectors/registry.json from every connectors/<engine>/<id>/meta.json.
3 + * Per-connector meta files avoid merge conflicts; registry.json stays the machine-readable
4 + * registry required by CLAUDE.md §106. Run: pnpm tsx scripts/build-registry.ts
5 + */
6 +import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
7 +import path from 'node:path';
8 +import { fileURLToPath } from 'node:url';
9 +import { RegistrySchema, ConnectorMetaSchema } from '../packages/connectors/src/types.ts';
10 +
11 +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
12 +const dir = path.join(root, 'connectors');
13 +const metas: unknown[] = [];
14 +for (const engine of readdirSync(dir)) {
15 + const p = path.join(dir, engine);
16 + if (!statSync(p).isDirectory() || engine === 'node_modules') continue;
17 + for (const id of readdirSync(p)) {
18 + const metaPath = path.join(p, id, 'meta.json');
19 + try {
20 + const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
21 + const parsed = ConnectorMetaSchema.parse({ ...meta, module: meta.module ?? `${engine}/${id}` });
22 + if (parsed.id !== id) throw new Error(`meta.id ${parsed.id} ≠ folder ${id}`);
23 + metas.push(parsed);
24 + } catch (err) {
25 + if ((err as NodeJS.ErrnoException).code === 'ENOENT') continue;
26 + throw new Error(`${metaPath}: ${err instanceof Error ? err.message : String(err)}`);
27 + }
28 + }
29 +}
30 +metas.sort((a, b) => ((a as { id: string }).id < (b as { id: string }).id ? -1 : 1));
31 +const registry = RegistrySchema.parse({ version: '1.0', connectors: metas });
32 +writeFileSync(path.join(dir, 'registry.json'), JSON.stringify(registry, null, 2) + '\n');
33 +console.log(`[registry] ${registry.connectors.length} connectors written to connectors/registry.json`);
added tsconfig.base.json +22 −0
@@ -0,0 +1,22 @@
1 +{
2 + "compilerOptions": {
3 + "target": "ES2023",
4 + "lib": ["ES2023"],
5 + "module": "NodeNext",
6 + "moduleResolution": "NodeNext",
7 + "strict": true,
8 + "noUncheckedIndexedAccess": true,
9 + "noImplicitOverride": true,
10 + "noFallthroughCasesInSwitch": true,
11 + "exactOptionalPropertyTypes": false,
12 + "esModuleInterop": true,
13 + "skipLibCheck": true,
14 + "resolveJsonModule": true,
15 + "declaration": true,
16 + "declarationMap": true,
17 + "sourceMap": true,
18 + "isolatedModules": true,
19 + "verbatimModuleSyntax": true,
20 + "forceConsistentCasingInFileNames": true
21 + }
22 +}
added vitest.workspace.ts +6 −0
@@ -0,0 +1,6 @@
1 +export default [
2 + 'packages/*/vitest.config.ts',
3 + 'connectors/vitest.config.ts',
4 + 'workers/vitest.config.ts',
5 + 'apps/api/vitest.config.ts',
6 +];
added workers/package.json +1 −0
@@ -0,0 +1 @@
1 +{"name":"@rareindex/workers","version":"0.1.0","private":true,"type":"module","scripts":{"typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run --passWithNoTests"},"dependencies":{"@rareindex/shared":"workspace:*"},"devDependencies":{"@types/node":"^24.0.0","typescript":"^5.9.3","vitest":"^3.2.0"}}
2