From 4a1e540f64529a46380c285b381a8f012f55a13b Mon Sep 17 00:00:00 2001 From: HarshCasper Date: Wed, 4 Feb 2026 19:00:37 +0530 Subject: [PATCH 1/3] first iteration --- paradedb/sample-movie-search/.gitignore | 29 + paradedb/sample-movie-search/Makefile | 91 + paradedb/sample-movie-search/README.md | 194 ++ paradedb/sample-movie-search/bin/app.ts | 7 + paradedb/sample-movie-search/cdk.json | 21 + paradedb/sample-movie-search/data/movies.json | 912 +++++++ paradedb/sample-movie-search/idea.md | 294 ++ paradedb/sample-movie-search/lambda/index.ts | 269 ++ .../lambda/package-lock.json | 2357 +++++++++++++++++ .../sample-movie-search/lambda/package.json | 20 + .../lib/movie-search-stack.ts | 181 ++ .../sample-movie-search/package-lock.json | 511 ++++ paradedb/sample-movie-search/package.json | 20 + paradedb/sample-movie-search/tsconfig.json | 26 + paradedb/sample-movie-search/web/index.html | 55 + paradedb/sample-movie-search/web/style.css | 277 ++ 16 files changed, 5264 insertions(+) create mode 100644 paradedb/sample-movie-search/.gitignore create mode 100644 paradedb/sample-movie-search/Makefile create mode 100644 paradedb/sample-movie-search/README.md create mode 100644 paradedb/sample-movie-search/bin/app.ts create mode 100644 paradedb/sample-movie-search/cdk.json create mode 100644 paradedb/sample-movie-search/data/movies.json create mode 100644 paradedb/sample-movie-search/idea.md create mode 100644 paradedb/sample-movie-search/lambda/index.ts create mode 100644 paradedb/sample-movie-search/lambda/package-lock.json create mode 100644 paradedb/sample-movie-search/lambda/package.json create mode 100644 paradedb/sample-movie-search/lib/movie-search-stack.ts create mode 100644 paradedb/sample-movie-search/package-lock.json create mode 100644 paradedb/sample-movie-search/package.json create mode 100644 paradedb/sample-movie-search/tsconfig.json create mode 100644 paradedb/sample-movie-search/web/index.html create mode 100644 paradedb/sample-movie-search/web/style.css diff --git a/paradedb/sample-movie-search/.gitignore b/paradedb/sample-movie-search/.gitignore new file mode 100644 index 00000000..9a2a24d6 --- /dev/null +++ b/paradedb/sample-movie-search/.gitignore @@ -0,0 +1,29 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +cdk.out/ +*.js +*.d.ts + +# Keep TypeScript source +!jest.config.js + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# Local env +.env +.env.local diff --git a/paradedb/sample-movie-search/Makefile b/paradedb/sample-movie-search/Makefile new file mode 100644 index 00000000..c14140d5 --- /dev/null +++ b/paradedb/sample-movie-search/Makefile @@ -0,0 +1,91 @@ +.PHONY: install deploy init seed destroy clean help web-ui test-search get-api-url + +help: + @echo "ParadeDB Movie Search Sample App" + @echo "" + @echo "Usage:" + @echo " make install - Install all dependencies" + @echo " make deploy - Deploy CDK stack to LocalStack" + @echo " make init - Initialize database schema and BM25 index" + @echo " make seed - Load movie data from S3 into ParadeDB" + @echo " make web-ui - Run the Web UI on localhost port 3000" + @echo " make destroy - Tear down the stack" + @echo " make clean - Remove build artifacts" + @echo "" + @echo "Quick start:" + @echo " make install && make deploy && make init && make seed && make web-ui" + +install: + @echo "Installing CDK dependencies..." + npm install + @echo "Installing Lambda dependencies..." + cd lambda && npm install + @echo "Done!" + +deploy: + @echo "Deploying MovieSearchStack to LocalStack..." + cdklocal bootstrap + cdklocal deploy --require-approval never + @echo "" + @echo "Deployment complete!" + +init: + @echo "Initializing database schema and BM25 index..." + @API_URL=$$(awslocal cloudformation describe-stacks \ + --stack-name MovieSearchStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \ + --output text 2>/dev/null); \ + if [ -z "$$API_URL" ]; then \ + echo "Error: Stack not deployed. Run 'make deploy' first."; \ + exit 1; \ + fi; \ + curl -s -X POST "$${API_URL}admin/init" | jq . + @echo "Database initialized!" + +seed: + @echo "Seeding movie data from S3..." + @API_URL=$$(awslocal cloudformation describe-stacks \ + --stack-name MovieSearchStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \ + --output text 2>/dev/null); \ + if [ -z "$$API_URL" ]; then \ + echo "Error: Stack not deployed. Run 'make deploy' first."; \ + exit 1; \ + fi; \ + curl -s -X POST "$${API_URL}admin/seed" | jq . + @echo "Data seeded!" + +test-search: + @echo "Testing search endpoint..." + @API_URL=$$(awslocal cloudformation describe-stacks \ + --stack-name MovieSearchStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \ + --output text 2>/dev/null); \ + if [ -z "$$API_URL" ]; then \ + echo "Error: Stack not deployed. Run 'make deploy' first."; \ + exit 1; \ + fi; \ + echo "Searching for 'redemption'..."; \ + curl -s "$${API_URL}search?q=redemption" | jq . + +destroy: + @echo "Destroying MovieSearchStack..." + cdklocal destroy --force + @echo "Stack destroyed!" + +clean: + rm -rf node_modules lambda/node_modules cdk.out dist + @echo "Cleaned!" + +get-api-url: + @awslocal cloudformation describe-stacks \ + --stack-name MovieSearchStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ApiEndpoint`].OutputValue' \ + --output text + +web-ui: + @echo "Starting Movie Search Web UI..." + @echo "API endpoint: http://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev" + @echo "" + @which serve > /dev/null 2>&1 || (echo "Installing serve..." && npm i -g serve) + serve -s ./web -l 3000 diff --git a/paradedb/sample-movie-search/README.md b/paradedb/sample-movie-search/README.md new file mode 100644 index 00000000..b25f6630 --- /dev/null +++ b/paradedb/sample-movie-search/README.md @@ -0,0 +1,194 @@ +# ParadeDB Movie Search Sample App + +A CDK application demonstrating ParadeDB's full-text search capabilities with LocalStack. + +## Overview + +This sample app deploys a serverless movie search application using: + +- **AWS Lambda** - Handles search and data operations +- **Amazon API Gateway** - REST API endpoints +- **Amazon S3** - Stores movie dataset +- **ParadeDB** - Full-text search engine (runs as LocalStack extension) + +### Features Demonstrated + +| Feature | Description | +|---------|-------------| +| **BM25 Ranking** | Industry-standard relevance scoring | +| **Fuzzy Matching** | Handles typos (e.g., "Godfater" finds "Godfather") | +| **Highlighting** | Returns matched text with highlighted terms | + +### API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/search?q=` | Search movies with BM25 ranking | +| GET | `/movies/{id}` | Get movie details by ID | +| POST | `/admin/init` | Initialize database schema | +| POST | `/admin/seed` | Load movie data from S3 | + +## Prerequisites + +- [LocalStack](https://localstack.cloud/) installed and running +- [Node.js](https://nodejs.org/) 18+ installed +- [AWS CDK Local](https://github.com/localstack/aws-cdk-local) (`npm install -g aws-cdk-local`) +- [AWS CLI](https://aws.amazon.com/cli/) configured +- ParadeDB extension installed in LocalStack + +## Setup + +### 1. Start LocalStack with ParadeDB Extension + +```bash +# Install the ParadeDB extension +localstack extensions install localstack-extension-paradedb + +# Start LocalStack +localstack start +``` + +### 2. Install Dependencies + +```bash +cd paradedb/sample-movie-search +make install +``` + +Or manually: + +```bash +npm install +cd lambda && npm install +``` + +### 3. Deploy the Stack + +```bash +make deploy +``` + +Or manually: + +```bash +cdklocal bootstrap +cdklocal deploy +``` + +After deployment, you'll see output similar to: + +``` +Outputs: +MovieSearchStack.ApiEndpoint = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/ +MovieSearchStack.DataBucketName = movie-search-data +MovieSearchStack.InitEndpoint = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/admin/init +MovieSearchStack.MovieSearchApiEndpointB25066EC = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/ +MovieSearchStack.MoviesEndpoint = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/movies/{id} +MovieSearchStack.SearchEndpoint = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/search +MovieSearchStack.SeedEndpoint = https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/admin/seed +``` + +### 4. Initialize Database + +Create the movies table and BM25 search index: + +```bash +make init +``` + +### 5. Seed Data + +Load movie data from S3 into ParadeDB: + +```bash +make seed +``` + +## Usage + +### Search Movies + +```bash +# Basic search +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=redemption" + +# With pagination +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=dark%20knight&limit=5&offset=0" + +# Fuzzy search (handles typos) +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=godfater" +``` + +### Get Movie Details + +```bash +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/movies/tt0111161" +``` + +### Example Response + +```json +{ + "success": true, + "data": { + "results": [ + { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "year": 1994, + "genres": ["Drama"], + "rating": 9.3, + "directors": ["Frank Darabont"], + "actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton"], + "highlight": "...finding solace and eventual redemption through acts of common decency." + } + ], + "total": 1, + "limit": 10, + "offset": 0 + } +} +``` + +## Web UI + +A minimal web UI is included in the `web/` directory. To use it: + +1. Open `web/index.html` in a browser +2. Set the API URL by opening the browser console and running: + +```javascript +setApiUrl('https://.execute-api.localhost.localstack.cloud:4566/dev') +``` + +3. Start searching! + +## How It Works + +1. **Deployment**: CDK creates Lambda functions, API Gateway, and S3 bucket with movie data + +2. **Initialization**: The init Lambda creates the movies table and ParadeDB BM25 index: + ```sql + CALL paradedb.create_bm25( + index_name => 'movies_search_idx', + table_name => 'movies', + key_field => 'id', + text_fields => paradedb.field('title') || paradedb.field('plot') + ); + ``` + +3. **Data Loading**: The seed Lambda reads `movies.json` from S3 and inserts into ParadeDB + +4. **Search**: Queries use ParadeDB's BM25 search with fuzzy matching: + ```sql + SELECT *, paradedb.snippet(plot) as highlight + FROM movies + WHERE id @@@ paradedb.parse('title:query~1 OR plot:query~1') + ORDER BY paradedb.score(id) DESC + ``` + +## References + +- [ParadeDB Documentation](https://docs.paradedb.com/) +- [LocalStack Extensions](https://docs.localstack.cloud/aws/tooling/extensions/) +- [AWS CDK Local](https://github.com/localstack/aws-cdk-local) diff --git a/paradedb/sample-movie-search/bin/app.ts b/paradedb/sample-movie-search/bin/app.ts new file mode 100644 index 00000000..7f43a301 --- /dev/null +++ b/paradedb/sample-movie-search/bin/app.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { MovieSearchStack } from "../lib/movie-search-stack"; + +const app = new cdk.App(); +new MovieSearchStack(app, "MovieSearchStack", {}); diff --git a/paradedb/sample-movie-search/cdk.json b/paradedb/sample-movie-search/cdk.json new file mode 100644 index 00000000..fd6d55af --- /dev/null +++ b/paradedb/sample-movie-search/cdk.json @@ -0,0 +1,21 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "node_modules", + "lambda/node_modules" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] + } +} diff --git a/paradedb/sample-movie-search/data/movies.json b/paradedb/sample-movie-search/data/movies.json new file mode 100644 index 00000000..3eea5418 --- /dev/null +++ b/paradedb/sample-movie-search/data/movies.json @@ -0,0 +1,912 @@ +[ + { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "year": 1994, + "genres": ["Drama"], + "rating": 9.3, + "directors": ["Frank Darabont"], + "actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton"], + "plot": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency." + }, + { + "id": "tt0068646", + "title": "The Godfather", + "year": 1972, + "genres": ["Crime", "Drama"], + "rating": 9.2, + "directors": ["Francis Ford Coppola"], + "actors": ["Marlon Brando", "Al Pacino", "James Caan"], + "plot": "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant youngest son." + }, + { + "id": "tt0468569", + "title": "The Dark Knight", + "year": 2008, + "genres": ["Action", "Crime", "Drama"], + "rating": 9.0, + "directors": ["Christopher Nolan"], + "actors": ["Christian Bale", "Heath Ledger", "Aaron Eckhart"], + "plot": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice." + }, + { + "id": "tt0071562", + "title": "The Godfather Part II", + "year": 1974, + "genres": ["Crime", "Drama"], + "rating": 9.0, + "directors": ["Francis Ford Coppola"], + "actors": ["Al Pacino", "Robert De Niro", "Robert Duvall"], + "plot": "The early life and career of Vito Corleone in 1920s New York City is portrayed, while his son, Michael, expands and tightens his grip on the family crime syndicate." + }, + { + "id": "tt0050083", + "title": "12 Angry Men", + "year": 1957, + "genres": ["Crime", "Drama"], + "rating": 9.0, + "directors": ["Sidney Lumet"], + "actors": ["Henry Fonda", "Lee J. Cobb", "Martin Balsam"], + "plot": "A jury holdout attempts to prevent a miscarriage of justice by forcing his colleagues to reconsider the evidence." + }, + { + "id": "tt0108052", + "title": "Schindler's List", + "year": 1993, + "genres": ["Biography", "Drama", "History"], + "rating": 9.0, + "directors": ["Steven Spielberg"], + "actors": ["Liam Neeson", "Ralph Fiennes", "Ben Kingsley"], + "plot": "In German-occupied Poland during World War II, industrialist Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis." + }, + { + "id": "tt0167260", + "title": "The Lord of the Rings: The Return of the King", + "year": 2003, + "genres": ["Action", "Adventure", "Drama"], + "rating": 9.0, + "directors": ["Peter Jackson"], + "actors": ["Elijah Wood", "Viggo Mortensen", "Ian McKellen"], + "plot": "Gandalf and Aragorn lead the World of Men against Sauron's army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring." + }, + { + "id": "tt0110912", + "title": "Pulp Fiction", + "year": 1994, + "genres": ["Crime", "Drama"], + "rating": 8.9, + "directors": ["Quentin Tarantino"], + "actors": ["John Travolta", "Uma Thurman", "Samuel L. Jackson"], + "plot": "The lives of two mob hitmen, a boxer, a gangster and his wife, and a pair of diner bandits intertwine in four tales of violence and redemption." + }, + { + "id": "tt0120737", + "title": "The Lord of the Rings: The Fellowship of the Ring", + "year": 2001, + "genres": ["Action", "Adventure", "Drama"], + "rating": 8.8, + "directors": ["Peter Jackson"], + "actors": ["Elijah Wood", "Ian McKellen", "Orlando Bloom"], + "plot": "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron." + }, + { + "id": "tt0137523", + "title": "Fight Club", + "year": 1999, + "genres": ["Drama"], + "rating": 8.8, + "directors": ["David Fincher"], + "actors": ["Brad Pitt", "Edward Norton", "Meat Loaf"], + "plot": "An insomniac office worker and a devil-may-care soapmaker form an underground fight club that evolves into something much, much more." + }, + { + "id": "tt0109830", + "title": "Forrest Gump", + "year": 1994, + "genres": ["Drama", "Romance"], + "rating": 8.8, + "directors": ["Robert Zemeckis"], + "actors": ["Tom Hanks", "Robin Wright", "Gary Sinise"], + "plot": "The presidencies of Kennedy and Johnson, the Vietnam War, the Watergate scandal and other historical events unfold from the perspective of an Alabama man with an IQ of 75." + }, + { + "id": "tt0167261", + "title": "The Lord of the Rings: The Two Towers", + "year": 2002, + "genres": ["Action", "Adventure", "Drama"], + "rating": 8.8, + "directors": ["Peter Jackson"], + "actors": ["Elijah Wood", "Ian McKellen", "Viggo Mortensen"], + "plot": "While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard." + }, + { + "id": "tt1375666", + "title": "Inception", + "year": 2010, + "genres": ["Action", "Adventure", "Sci-Fi"], + "rating": 8.8, + "directors": ["Christopher Nolan"], + "actors": ["Leonardo DiCaprio", "Joseph Gordon-Levitt", "Elliot Page"], + "plot": "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O." + }, + { + "id": "tt0080684", + "title": "Star Wars: Episode V - The Empire Strikes Back", + "year": 1980, + "genres": ["Action", "Adventure", "Fantasy"], + "rating": 8.7, + "directors": ["Irvin Kershner"], + "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], + "plot": "After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda, while his friends are pursued by Darth Vader." + }, + { + "id": "tt0133093", + "title": "The Matrix", + "year": 1999, + "genres": ["Action", "Sci-Fi"], + "rating": 8.7, + "directors": ["Lana Wachowski", "Lilly Wachowski"], + "actors": ["Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss"], + "plot": "When a beautiful stranger leads computer hacker Neo to a forbidding underworld, he discovers the shocking truth--the life he knows is the elaborate deception of an evil cyber-intelligence." + }, + { + "id": "tt0099685", + "title": "Goodfellas", + "year": 1990, + "genres": ["Biography", "Crime", "Drama"], + "rating": 8.7, + "directors": ["Martin Scorsese"], + "actors": ["Robert De Niro", "Ray Liotta", "Joe Pesci"], + "plot": "The story of Henry Hill and his life in the mob, covering his relationship with his wife Karen Hill and his mob partners Jimmy Conway and Tommy DeVito." + }, + { + "id": "tt0073486", + "title": "One Flew Over the Cuckoo's Nest", + "year": 1975, + "genres": ["Drama"], + "rating": 8.7, + "directors": ["Milos Forman"], + "actors": ["Jack Nicholson", "Louise Fletcher", "Will Sampson"], + "plot": "A criminal pleads insanity and is admitted to a mental institution, where he rebels against the oppressive nurse and rallies up the scared patients." + }, + { + "id": "tt0114369", + "title": "Se7en", + "year": 1995, + "genres": ["Crime", "Drama", "Mystery"], + "rating": 8.6, + "directors": ["David Fincher"], + "actors": ["Morgan Freeman", "Brad Pitt", "Kevin Spacey"], + "plot": "Two detectives, a rookie and a veteran, hunt a serial killer who uses the seven deadly sins as his motives." + }, + { + "id": "tt0038650", + "title": "It's a Wonderful Life", + "year": 1946, + "genres": ["Drama", "Family", "Fantasy"], + "rating": 8.6, + "directors": ["Frank Capra"], + "actors": ["James Stewart", "Donna Reed", "Lionel Barrymore"], + "plot": "An angel is sent from Heaven to help a desperately frustrated businessman by showing him what life would have been like if he had never existed." + }, + { + "id": "tt0102926", + "title": "The Silence of the Lambs", + "year": 1991, + "genres": ["Crime", "Drama", "Thriller"], + "rating": 8.6, + "directors": ["Jonathan Demme"], + "actors": ["Jodie Foster", "Anthony Hopkins", "Lawrence A. Bonney"], + "plot": "A young F.B.I. cadet must receive the help of an incarcerated and manipulative cannibal killer to help catch another serial killer, a madman who skins his victims." + }, + { + "id": "tt0076759", + "title": "Star Wars: Episode IV - A New Hope", + "year": 1977, + "genres": ["Action", "Adventure", "Fantasy"], + "rating": 8.6, + "directors": ["George Lucas"], + "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], + "plot": "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from the Empire's world-destroying battle station." + }, + { + "id": "tt0120689", + "title": "The Green Mile", + "year": 1999, + "genres": ["Crime", "Drama", "Fantasy"], + "rating": 8.6, + "directors": ["Frank Darabont"], + "actors": ["Tom Hanks", "Michael Clarke Duncan", "David Morse"], + "plot": "The lives of guards on Death Row are affected by one of their charges: a black man accused of child murder and rape, yet who has a mysterious gift." + }, + { + "id": "tt0816692", + "title": "Interstellar", + "year": 2014, + "genres": ["Adventure", "Drama", "Sci-Fi"], + "rating": 8.6, + "directors": ["Christopher Nolan"], + "actors": ["Matthew McConaughey", "Anne Hathaway", "Jessica Chastain"], + "plot": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival." + }, + { + "id": "tt0245429", + "title": "Spirited Away", + "year": 2001, + "genres": ["Animation", "Adventure", "Family"], + "rating": 8.6, + "directors": ["Hayao Miyazaki"], + "actors": ["Daveigh Chase", "Suzanne Pleshette", "Miyu Irino"], + "plot": "During her family's move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods, witches, and spirits, and where humans are changed into beasts." + }, + { + "id": "tt0120815", + "title": "Saving Private Ryan", + "year": 1998, + "genres": ["Drama", "War"], + "rating": 8.6, + "directors": ["Steven Spielberg"], + "actors": ["Tom Hanks", "Matt Damon", "Tom Sizemore"], + "plot": "Following the Normandy Landings, a group of U.S. soldiers go behind enemy lines to retrieve a paratrooper whose brothers have been killed in action." + }, + { + "id": "tt0047478", + "title": "Seven Samurai", + "year": 1954, + "genres": ["Action", "Adventure", "Drama"], + "rating": 8.6, + "directors": ["Akira Kurosawa"], + "actors": ["Toshiro Mifune", "Takashi Shimura", "Keiko Tsushima"], + "plot": "A poor village under attack by bandits recruits seven unemployed samurai to help them defend themselves." + }, + { + "id": "tt0103064", + "title": "Terminator 2: Judgment Day", + "year": 1991, + "genres": ["Action", "Sci-Fi"], + "rating": 8.6, + "directors": ["James Cameron"], + "actors": ["Arnold Schwarzenegger", "Linda Hamilton", "Edward Furlong"], + "plot": "A cyborg, identical to the one who failed to kill Sarah Connor, must now protect her teenage son, John Connor, from a more advanced and powerful cyborg." + }, + { + "id": "tt0054215", + "title": "Psycho", + "year": 1960, + "genres": ["Horror", "Mystery", "Thriller"], + "rating": 8.5, + "directors": ["Alfred Hitchcock"], + "actors": ["Anthony Perkins", "Janet Leigh", "Vera Miles"], + "plot": "A Phoenix secretary embezzles forty thousand dollars from her employer's client, goes on the run, and checks into a remote motel run by a young man under the domination of his mother." + }, + { + "id": "tt0027977", + "title": "Modern Times", + "year": 1936, + "genres": ["Comedy", "Drama", "Family"], + "rating": 8.5, + "directors": ["Charles Chaplin"], + "actors": ["Charles Chaplin", "Paulette Goddard", "Henry Bergman"], + "plot": "The Tramp struggles to live in modern industrial society with the help of a young homeless woman." + }, + { + "id": "tt0114814", + "title": "The Usual Suspects", + "year": 1995, + "genres": ["Crime", "Drama", "Mystery"], + "rating": 8.5, + "directors": ["Bryan Singer"], + "actors": ["Kevin Spacey", "Gabriel Byrne", "Chazz Palminteri"], + "plot": "A sole survivor tells of the twisty events leading up to a horrific gun battle on a boat, which began when five criminals met at a seemingly random police lineup." + }, + { + "id": "tt0021749", + "title": "City Lights", + "year": 1931, + "genres": ["Comedy", "Drama", "Romance"], + "rating": 8.5, + "directors": ["Charles Chaplin"], + "actors": ["Charles Chaplin", "Virginia Cherrill", "Florence Lee"], + "plot": "With the aid of a wealthy erratic tippler, a tramp who has fallen in love with a blind flower girl accumulates money to be able to help her with an operation that will restore her sight." + }, + { + "id": "tt0034583", + "title": "Casablanca", + "year": 1942, + "genres": ["Drama", "Romance", "War"], + "rating": 8.5, + "directors": ["Michael Curtiz"], + "actors": ["Humphrey Bogart", "Ingrid Bergman", "Paul Henreid"], + "plot": "A cynical expatriate American cafe owner struggles to decide whether or not to help his former lover and her fugitive husband escape the Nazis in French Morocco." + }, + { + "id": "tt0064116", + "title": "Once Upon a Time in the West", + "year": 1968, + "genres": ["Western"], + "rating": 8.5, + "directors": ["Sergio Leone"], + "actors": ["Henry Fonda", "Charles Bronson", "Claudia Cardinale"], + "plot": "A mysterious stranger with a harmonica joins forces with a notorious desperado to protect a beautiful widow from a ruthless assassin working for the railroad." + }, + { + "id": "tt0082971", + "title": "Raiders of the Lost Ark", + "year": 1981, + "genres": ["Action", "Adventure"], + "rating": 8.4, + "directors": ["Steven Spielberg"], + "actors": ["Harrison Ford", "Karen Allen", "Paul Freeman"], + "plot": "In 1936, archaeologist and adventurer Indiana Jones is hired by the U.S. government to find the Ark of the Covenant before Adolf Hitler's Nazis can obtain its awesome powers." + }, + { + "id": "tt0078788", + "title": "Apocalypse Now", + "year": 1979, + "genres": ["Drama", "Mystery", "War"], + "rating": 8.4, + "directors": ["Francis Ford Coppola"], + "actors": ["Martin Sheen", "Marlon Brando", "Robert Duvall"], + "plot": "A U.S. Army officer serving in Vietnam is tasked with assassinating a renegade Special Forces Colonel who sees himself as a god." + }, + { + "id": "tt0078748", + "title": "Alien", + "year": 1979, + "genres": ["Horror", "Sci-Fi"], + "rating": 8.4, + "directors": ["Ridley Scott"], + "actors": ["Sigourney Weaver", "Tom Skerritt", "John Hurt"], + "plot": "After a space merchant vessel receives an unknown transmission as a distress call, one of the crew is attacked by a mysterious life form and they soon realize that its life cycle has merely begun." + }, + { + "id": "tt0209144", + "title": "Memento", + "year": 2000, + "genres": ["Mystery", "Thriller"], + "rating": 8.4, + "directors": ["Christopher Nolan"], + "actors": ["Guy Pearce", "Carrie-Anne Moss", "Joe Pantoliano"], + "plot": "A man with short-term memory loss attempts to track down his wife's murderer." + }, + { + "id": "tt0090605", + "title": "Aliens", + "year": 1986, + "genres": ["Action", "Adventure", "Sci-Fi"], + "rating": 8.4, + "directors": ["James Cameron"], + "actors": ["Sigourney Weaver", "Michael Biehn", "Carrie Henn"], + "plot": "Ellen Ripley is rescued by a deep salvage team after being in hypersleep for 57 years. The moon that the Nostromo visited has been colonized, but contact is lost." + }, + { + "id": "tt0172495", + "title": "Gladiator", + "year": 2000, + "genres": ["Action", "Adventure", "Drama"], + "rating": 8.5, + "directors": ["Ridley Scott"], + "actors": ["Russell Crowe", "Joaquin Phoenix", "Connie Nielsen"], + "plot": "A former Roman General sets out to exact vengeance against the corrupt emperor who murdered his family and sent him into slavery." + }, + { + "id": "tt0407887", + "title": "The Departed", + "year": 2006, + "genres": ["Crime", "Drama", "Thriller"], + "rating": 8.5, + "directors": ["Martin Scorsese"], + "actors": ["Leonardo DiCaprio", "Matt Damon", "Jack Nicholson"], + "plot": "An undercover cop and a mole in the police attempt to identify each other while infiltrating an Irish gang in South Boston." + }, + { + "id": "tt0482571", + "title": "The Prestige", + "year": 2006, + "genres": ["Drama", "Mystery", "Sci-Fi"], + "rating": 8.5, + "directors": ["Christopher Nolan"], + "actors": ["Christian Bale", "Hugh Jackman", "Scarlett Johansson"], + "plot": "After a tragic accident, two stage magicians engage in a battle to create the ultimate illusion while sacrificing everything they have to outwit each other." + }, + { + "id": "tt0032553", + "title": "The Great Dictator", + "year": 1940, + "genres": ["Comedy", "Drama", "War"], + "rating": 8.4, + "directors": ["Charles Chaplin"], + "actors": ["Charles Chaplin", "Paulette Goddard", "Jack Oakie"], + "plot": "Dictator Adenoid Hynkel tries to expand his empire while a poor Jewish barber tries to avoid persecution from Hynkel's regime." + }, + { + "id": "tt0057012", + "title": "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", + "year": 1964, + "genres": ["Comedy", "War"], + "rating": 8.4, + "directors": ["Stanley Kubrick"], + "actors": ["Peter Sellers", "George C. Scott", "Sterling Hayden"], + "plot": "An insane American general orders a bombing attack on the Soviet Union, triggering a path to nuclear holocaust that a war room full of politicians and generals frantically tries to stop." + }, + { + "id": "tt0095765", + "title": "Cinema Paradiso", + "year": 1988, + "genres": ["Drama", "Romance"], + "rating": 8.5, + "directors": ["Giuseppe Tornatore"], + "actors": ["Philippe Noiret", "Enzo Cannavale", "Antonella Attili"], + "plot": "A filmmaker recalls his childhood when falling in love with the pictures at the cinema of his home village and forms a deep friendship with the cinema's projectionist." + }, + { + "id": "tt0253474", + "title": "The Pianist", + "year": 2002, + "genres": ["Biography", "Drama", "Music"], + "rating": 8.5, + "directors": ["Roman Polanski"], + "actors": ["Adrien Brody", "Thomas Kretschmann", "Frank Finlay"], + "plot": "A Polish Jewish musician struggles to survive the destruction of the Warsaw ghetto of World War II." + }, + { + "id": "tt0110413", + "title": "Leon: The Professional", + "year": 1994, + "genres": ["Action", "Crime", "Drama"], + "rating": 8.5, + "directors": ["Luc Besson"], + "actors": ["Jean Reno", "Gary Oldman", "Natalie Portman"], + "plot": "Mathilda, a 12-year-old girl, is reluctantly taken in by Leon, a professional assassin, after her family is murdered. An unusual relationship forms as she becomes his protegee and learns the assassin's trade." + }, + { + "id": "tt0088763", + "title": "Back to the Future", + "year": 1985, + "genres": ["Adventure", "Comedy", "Sci-Fi"], + "rating": 8.5, + "directors": ["Robert Zemeckis"], + "actors": ["Michael J. Fox", "Christopher Lloyd", "Lea Thompson"], + "plot": "Marty McFly, a 17-year-old high school student, is accidentally sent thirty years into the past in a time-traveling DeLorean invented by his close friend, the eccentric scientist Doc Brown." + }, + { + "id": "tt0056172", + "title": "Lawrence of Arabia", + "year": 1962, + "genres": ["Adventure", "Biography", "Drama"], + "rating": 8.3, + "directors": ["David Lean"], + "actors": ["Peter O'Toole", "Alec Guinness", "Anthony Quinn"], + "plot": "The story of T.E. Lawrence, the English officer who successfully united and led the diverse, often warring, Arab tribes during World War I in order to fight the Turks." + }, + { + "id": "tt0119698", + "title": "Princess Mononoke", + "year": 1997, + "genres": ["Animation", "Action", "Adventure"], + "rating": 8.4, + "directors": ["Hayao Miyazaki"], + "actors": ["Yoji Matsuda", "Yuriko Ishida", "Yuko Tanaka"], + "plot": "On a journey to find the cure for a Tatarigami's curse, Ashitaka finds himself in the middle of a war between the forest gods and Tatara, a mining colony." + }, + { + "id": "tt0056592", + "title": "To Kill a Mockingbird", + "year": 1962, + "genres": ["Crime", "Drama"], + "rating": 8.3, + "directors": ["Robert Mulligan"], + "actors": ["Gregory Peck", "John Megna", "Frank Overton"], + "plot": "Atticus Finch, a widowed lawyer in Depression-era Alabama, defends a black man against an undeserved rape charge, and his children against prejudice." + }, + { + "id": "tt0040522", + "title": "Bicycle Thieves", + "year": 1948, + "genres": ["Drama"], + "rating": 8.3, + "directors": ["Vittorio De Sica"], + "actors": ["Lamberto Maggiorani", "Enzo Staiola", "Lianella Carell"], + "plot": "In post-war Italy, a working-class man's bicycle is stolen. He and his son set out to find it." + }, + { + "id": "tt0042876", + "title": "Sunset Boulevard", + "year": 1950, + "genres": ["Drama", "Film-Noir"], + "rating": 8.4, + "directors": ["Billy Wilder"], + "actors": ["William Holden", "Gloria Swanson", "Erich von Stroheim"], + "plot": "A screenwriter develops a dangerous relationship with a faded film star determined to make a triumphant return." + }, + { + "id": "tt0051201", + "title": "Witness for the Prosecution", + "year": 1957, + "genres": ["Crime", "Drama", "Mystery"], + "rating": 8.4, + "directors": ["Billy Wilder"], + "actors": ["Tyrone Power", "Marlene Dietrich", "Charles Laughton"], + "plot": "A veteran British barrister must defend his client in a murder trial that has surprise after surprise." + }, + { + "id": "tt0081505", + "title": "The Shining", + "year": 1980, + "genres": ["Drama", "Horror"], + "rating": 8.4, + "directors": ["Stanley Kubrick"], + "actors": ["Jack Nicholson", "Shelley Duvall", "Danny Lloyd"], + "plot": "A family heads to an isolated hotel for the winter where a sinister presence influences the father into violence, while his psychic son sees horrific forebodings from both past and future." + }, + { + "id": "tt0044741", + "title": "Ikiru", + "year": 1952, + "genres": ["Drama"], + "rating": 8.3, + "directors": ["Akira Kurosawa"], + "actors": ["Takashi Shimura", "Nobuo Kaneko", "Shin'ichi Himori"], + "plot": "A bureaucrat tries to find meaning in his life after he discovers he has terminal cancer." + }, + { + "id": "tt0087843", + "title": "Once Upon a Time in America", + "year": 1984, + "genres": ["Crime", "Drama"], + "rating": 8.3, + "directors": ["Sergio Leone"], + "actors": ["Robert De Niro", "James Woods", "Elizabeth McGovern"], + "plot": "A former Prohibition-era Jewish gangster returns to the Lower East Side of Manhattan over thirty years later, where he once again must confront the ghosts and regrets of his old life." + }, + { + "id": "tt0405094", + "title": "The Lives of Others", + "year": 2006, + "genres": ["Drama", "Mystery", "Thriller"], + "rating": 8.4, + "directors": ["Florian Henckel von Donnersmarck"], + "actors": ["Ulrich Muhe", "Martina Gedeck", "Sebastian Koch"], + "plot": "In 1984 East Berlin, an idealistic Stasi agent is assigned to monitor a successful playwright and his partner, but he gradually becomes absorbed by their lives." + }, + { + "id": "tt0086190", + "title": "Star Wars: Episode VI - Return of the Jedi", + "year": 1983, + "genres": ["Action", "Adventure", "Fantasy"], + "rating": 8.3, + "directors": ["Richard Marquand"], + "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], + "plot": "After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star." + }, + { + "id": "tt0086879", + "title": "Amadeus", + "year": 1984, + "genres": ["Biography", "Drama", "Music"], + "rating": 8.3, + "directors": ["Milos Forman"], + "actors": ["F. Murray Abraham", "Tom Hulce", "Elizabeth Berridge"], + "plot": "The life, success and troubles of Wolfgang Amadeus Mozart, as told by Antonio Salieri, the contemporaneous composer who was insanely jealous of Mozart's talent and claimed to have murdered him." + }, + { + "id": "tt0052357", + "title": "Vertigo", + "year": 1958, + "genres": ["Mystery", "Romance", "Thriller"], + "rating": 8.3, + "directors": ["Alfred Hitchcock"], + "actors": ["James Stewart", "Kim Novak", "Barbara Bel Geddes"], + "plot": "A former San Francisco police detective juggles wrestling with his personal demons and becoming obsessed with the hauntingly beautiful woman he has been hired to follow." + }, + { + "id": "tt0169547", + "title": "American Beauty", + "year": 1999, + "genres": ["Drama"], + "rating": 8.3, + "directors": ["Sam Mendes"], + "actors": ["Kevin Spacey", "Annette Bening", "Thora Birch"], + "plot": "A sexually frustrated suburban father has a mid-life crisis after becoming infatuated with his daughter's best friend." + }, + { + "id": "tt0091251", + "title": "Come and See", + "year": 1985, + "genres": ["Drama", "Thriller", "War"], + "rating": 8.4, + "directors": ["Elem Klimov"], + "actors": ["Aleksei Kravchenko", "Olga Mironova", "Liubomiras Laucevicius"], + "plot": "After finding an old rifle, a young boy joins the Soviet resistance movement against ruthless German forces and experiences the horrors of World War II." + }, + { + "id": "tt1853728", + "title": "Django Unchained", + "year": 2012, + "genres": ["Drama", "Western"], + "rating": 8.4, + "directors": ["Quentin Tarantino"], + "actors": ["Jamie Foxx", "Christoph Waltz", "Leonardo DiCaprio"], + "plot": "With the help of a German bounty hunter, a freed slave sets out to rescue his wife from a brutal Mississippi plantation owner." + }, + { + "id": "tt0053125", + "title": "North by Northwest", + "year": 1959, + "genres": ["Action", "Adventure", "Mystery"], + "rating": 8.3, + "directors": ["Alfred Hitchcock"], + "actors": ["Cary Grant", "Eva Marie Saint", "James Mason"], + "plot": "A New York City advertising executive goes on the run after being mistaken for a government agent by a group of foreign spies." + }, + { + "id": "tt0033467", + "title": "Citizen Kane", + "year": 1941, + "genres": ["Drama", "Mystery"], + "rating": 8.3, + "directors": ["Orson Welles"], + "actors": ["Orson Welles", "Joseph Cotten", "Dorothy Comingore"], + "plot": "Following the death of publishing tycoon Charles Foster Kane, reporters scramble to uncover the meaning of his final utterance: 'Rosebud.'" + }, + { + "id": "tt0095327", + "title": "Grave of the Fireflies", + "year": 1988, + "genres": ["Animation", "Drama", "War"], + "rating": 8.5, + "directors": ["Isao Takahata"], + "actors": ["Tsutomu Tatsumi", "Ayano Shiraishi", "Akemi Yamaguchi"], + "plot": "A young boy and his little sister struggle to survive in Japan during World War II." + }, + { + "id": "tt0045152", + "title": "Singin' in the Rain", + "year": 1952, + "genres": ["Comedy", "Musical", "Romance"], + "rating": 8.3, + "directors": ["Stanley Donen", "Gene Kelly"], + "actors": ["Gene Kelly", "Donald O'Connor", "Debbie Reynolds"], + "plot": "A silent film star falls for a chorus girl just as he and his studio are about to make the difficult transition to sound films." + }, + { + "id": "tt0053604", + "title": "The Apartment", + "year": 1960, + "genres": ["Comedy", "Drama", "Romance"], + "rating": 8.3, + "directors": ["Billy Wilder"], + "actors": ["Jack Lemmon", "Shirley MacLaine", "Fred MacMurray"], + "plot": "A man tries to rise in his company by letting its executives use his apartment for trysts, but complications and a potential love interest arise." + }, + { + "id": "tt0364569", + "title": "Oldboy", + "year": 2003, + "genres": ["Action", "Drama", "Mystery"], + "rating": 8.4, + "directors": ["Park Chan-wook"], + "actors": ["Choi Min-sik", "Yoo Ji-tae", "Kang Hye-jung"], + "plot": "After being kidnapped and imprisoned for fifteen years, Oh Dae-Su is released, only to find that he must find his captor in five days." + }, + { + "id": "tt0062622", + "title": "2001: A Space Odyssey", + "year": 1968, + "genres": ["Adventure", "Sci-Fi"], + "rating": 8.3, + "directors": ["Stanley Kubrick"], + "actors": ["Keir Dullea", "Gary Lockwood", "William Sylvester"], + "plot": "After discovering a mysterious artifact buried beneath the Lunar surface, mankind sets off on a quest to find its origins with help from intelligent supercomputer H.A.L. 9000." + }, + { + "id": "tt0211915", + "title": "Amelie", + "year": 2001, + "genres": ["Comedy", "Romance"], + "rating": 8.3, + "directors": ["Jean-Pierre Jeunet"], + "actors": ["Audrey Tautou", "Mathieu Kassovitz", "Rufus"], + "plot": "Amelie is an innocent and naive girl in Paris with her own sense of justice. She decides to help those around her and, along the way, discovers love." + }, + { + "id": "tt0040897", + "title": "The Treasure of the Sierra Madre", + "year": 1948, + "genres": ["Adventure", "Drama", "Western"], + "rating": 8.2, + "directors": ["John Huston"], + "actors": ["Humphrey Bogart", "Walter Huston", "Tim Holt"], + "plot": "Two American men searching for work in Mexico convince an old prospector to help them mine for gold in the Sierra Madre Mountains." + }, + { + "id": "tt0036775", + "title": "Double Indemnity", + "year": 1944, + "genres": ["Crime", "Drama", "Film-Noir"], + "rating": 8.3, + "directors": ["Billy Wilder"], + "actors": ["Fred MacMurray", "Barbara Stanwyck", "Edward G. Robinson"], + "plot": "A Los Angeles insurance representative lets an alluring housewife seduce him into a scheme of murder and insurance fraud." + }, + { + "id": "tt2582802", + "title": "Whiplash", + "year": 2014, + "genres": ["Drama", "Music"], + "rating": 8.5, + "directors": ["Damien Chazelle"], + "actors": ["Miles Teller", "J.K. Simmons", "Melissa Benoist"], + "plot": "A promising young drummer enrolls at a cut-throat music conservatory where his dreams of greatness are mentored by an instructor who will stop at nothing to realize a student's potential." + }, + { + "id": "tt0075314", + "title": "Taxi Driver", + "year": 1976, + "genres": ["Crime", "Drama"], + "rating": 8.2, + "directors": ["Martin Scorsese"], + "actors": ["Robert De Niro", "Jodie Foster", "Cybill Shepherd"], + "plot": "A mentally unstable veteran works as a nighttime taxi driver in New York City, where the perceived decadence and sleaze fuels his unrelenting sense of disenfranchisement." + }, + { + "id": "tt0093058", + "title": "Full Metal Jacket", + "year": 1987, + "genres": ["Drama", "War"], + "rating": 8.3, + "directors": ["Stanley Kubrick"], + "actors": ["Matthew Modine", "R. Lee Ermey", "Vincent D'Onofrio"], + "plot": "A pragmatic U.S. Marine observes the dehumanizing effects the Vietnam War has on his fellow recruits from their brutal boot camp training to the bloody street fighting in Hue." + }, + { + "id": "tt0317248", + "title": "City of God", + "year": 2002, + "genres": ["Crime", "Drama"], + "rating": 8.6, + "directors": ["Fernando Meirelles", "Katia Lund"], + "actors": ["Alexandre Rodrigues", "Leandro Firmino", "Matheus Nachtergaele"], + "plot": "In the slums of Rio, two kids' paths diverge as one struggles to become a photographer and the other a drug dealer." + }, + { + "id": "tt0070735", + "title": "The Sting", + "year": 1973, + "genres": ["Comedy", "Crime", "Drama"], + "rating": 8.3, + "directors": ["George Roy Hill"], + "actors": ["Paul Newman", "Robert Redford", "Robert Shaw"], + "plot": "Two grifters team up to pull off the ultimate con." + }, + { + "id": "tt0059578", + "title": "For a Few Dollars More", + "year": 1965, + "genres": ["Western"], + "rating": 8.2, + "directors": ["Sergio Leone"], + "actors": ["Clint Eastwood", "Lee Van Cleef", "Gian Maria Volonte"], + "plot": "Two bounty hunters with the same intentions team up to track down an escaped Mexican outlaw." + }, + { + "id": "tt0180093", + "title": "Requiem for a Dream", + "year": 2000, + "genres": ["Drama"], + "rating": 8.3, + "directors": ["Darren Aronofsky"], + "actors": ["Ellen Burstyn", "Jared Leto", "Jennifer Connelly"], + "plot": "The drug-induced utopias of four Coney Island people are shattered when their addictions run deep." + }, + { + "id": "tt0112573", + "title": "Braveheart", + "year": 1995, + "genres": ["Biography", "Drama", "History"], + "rating": 8.3, + "directors": ["Mel Gibson"], + "actors": ["Mel Gibson", "Sophie Marceau", "Patrick McGoohan"], + "plot": "Scottish warrior William Wallace leads his countrymen in a rebellion to free his homeland from the tyranny of King Edward I of England." + }, + { + "id": "tt0047396", + "title": "Rear Window", + "year": 1954, + "genres": ["Mystery", "Thriller"], + "rating": 8.5, + "directors": ["Alfred Hitchcock"], + "actors": ["James Stewart", "Grace Kelly", "Wendell Corey"], + "plot": "A wheelchair-bound photographer spies on his neighbors from his Greenwich Village courtyard apartment and becomes convinced one of them has committed murder." + }, + { + "id": "tt0892769", + "title": "How to Train Your Dragon", + "year": 2010, + "genres": ["Animation", "Action", "Adventure"], + "rating": 8.1, + "directors": ["Dean DeBlois", "Chris Sanders"], + "actors": ["Jay Baruchel", "Gerard Butler", "Christopher Mintz-Plasse"], + "plot": "A hapless young Viking who aspires to hunt dragons becomes the unlikely friend of a young dragon himself." + }, + { + "id": "tt0986264", + "title": "Taare Zameen Par", + "year": 2007, + "genres": ["Drama", "Family"], + "rating": 8.4, + "directors": ["Aamir Khan", "Amole Gupte"], + "actors": ["Darsheel Safary", "Aamir Khan", "Tanay Chheda"], + "plot": "An eight-year-old boy is thought to be a lazy trouble-maker, until the new art teacher has the patience and determination to discover the real problem behind his struggles." + }, + { + "id": "tt0361748", + "title": "Inglourious Basterds", + "year": 2009, + "genres": ["Adventure", "Drama", "War"], + "rating": 8.3, + "directors": ["Quentin Tarantino"], + "actors": ["Brad Pitt", "Diane Kruger", "Eli Roth"], + "plot": "In Nazi-occupied France during World War II, a plan to assassinate Nazi leaders by a group of Jewish U.S. soldiers coincides with a theatre owner's vengeful plans." + }, + { + "id": "tt0338013", + "title": "Eternal Sunshine of the Spotless Mind", + "year": 2004, + "genres": ["Drama", "Romance", "Sci-Fi"], + "rating": 8.3, + "directors": ["Michel Gondry"], + "actors": ["Jim Carrey", "Kate Winslet", "Tom Wilkinson"], + "plot": "When their relationship turns sour, a couple undergoes a medical procedure to have each other erased from their memories." + }, + { + "id": "tt0057115", + "title": "The Great Escape", + "year": 1963, + "genres": ["Adventure", "Drama", "History"], + "rating": 8.2, + "directors": ["John Sturges"], + "actors": ["Steve McQueen", "James Garner", "Richard Attenborough"], + "plot": "Allied prisoners of war plan for several hundred of their number to escape from a German camp during World War II." + }, + { + "id": "tt0017136", + "title": "Metropolis", + "year": 1927, + "genres": ["Drama", "Sci-Fi"], + "rating": 8.3, + "directors": ["Fritz Lang"], + "actors": ["Brigitte Helm", "Alfred Abel", "Gustav Frohlich"], + "plot": "In a futuristic city sharply divided between the working class and the city planners, the son of the city's mastermind falls in love with a working-class prophet." + }, + { + "id": "tt0043014", + "title": "Sunset Blvd.", + "year": 1950, + "genres": ["Drama", "Film-Noir"], + "rating": 8.4, + "directors": ["Billy Wilder"], + "actors": ["William Holden", "Gloria Swanson", "Erich von Stroheim"], + "plot": "A screenwriter develops a dangerous relationship with a faded film star determined to make a triumphant return." + }, + { + "id": "tt0476735", + "title": "My Father and My Son", + "year": 2005, + "genres": ["Drama", "Family"], + "rating": 8.2, + "directors": ["Cagan Irmak"], + "actors": ["Fikret Kuskan", "Cetin Tekindor", "Humeyra"], + "plot": "The political turmoil in 1980s Turkey creates a rift between a father and his son." + }, + { + "id": "tt0097576", + "title": "Indiana Jones and the Last Crusade", + "year": 1989, + "genres": ["Action", "Adventure"], + "rating": 8.2, + "directors": ["Steven Spielberg"], + "actors": ["Harrison Ford", "Sean Connery", "Alison Doody"], + "plot": "In 1938, after his father Professor Henry Jones, Sr. goes missing while pursuing the Holy Grail, Indiana Jones finds himself up against Adolf Hitler's Nazis again." + } +] diff --git a/paradedb/sample-movie-search/idea.md b/paradedb/sample-movie-search/idea.md new file mode 100644 index 00000000..48a277ab --- /dev/null +++ b/paradedb/sample-movie-search/idea.md @@ -0,0 +1,294 @@ +# ParadeDB Sample App: Movie Search + +## Overview + +A CDK application demonstrating integration with ParadeDB's full-text search capabilities running in LocalStack. This sample app showcases ParadeDB as a modern Elasticsearch replacement, using BM25 search with fuzzy matching and highlighting. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ LocalStack │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ │ │ │ │ │ │ +│ │ API Gateway │────▶│ Lambda │────▶│ ParadeDB │ │ +│ │ │ │ │ │ (pg_search) │ │ +│ └──────────────┘ └──────────────┘ └──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ S3 │ │ +│ │ (movie data) │ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌──────────────────┐ +│ Static Web UI │ +│ (HTML/CSS/JS) │ +└──────────────────┘ +``` + +## Tech Stack + +| Component | Technology | +|-----------|------------| +| Infrastructure | AWS CDK (TypeScript) | +| Lambda Runtime | Node.js 22.x | +| Database | ParadeDB (PostgreSQL + pg_search) | +| Postgres Client | pg (node-postgres) | +| Data Storage | Amazon S3 | +| API | Amazon API Gateway (REST) | +| Frontend | Vanilla HTML/CSS/JS | + +## AWS Services Used + +- **AWS Lambda** - Handles search and data operations +- **Amazon API Gateway** - REST API endpoints +- **Amazon S3** - Stores movie dataset (JSON) +- **ParadeDB Extension** - Full-text search engine (runs in LocalStack) + +## API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/search?q=` | Search movies with BM25 ranking | +| GET | `/movies/:id` | Get movie details by ID | + +### Search Query Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `q` | string | required | Search query (supports fuzzy matching) | +| `limit` | number | 10 | Max results to return | +| `offset` | number | 0 | Pagination offset | + +### Search Response Shape + +```json +{ + "success": true, + "data": { + "results": [ + { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "year": 1994, + "genres": ["Drama"], + "rating": 9.3, + "directors": ["Frank Darabont"], + "actors": ["Tim Robbins", "Morgan Freeman"], + "highlight": "...two imprisoned men bond over a number of years..." + } + ], + "total": 1, + "limit": 10, + "offset": 0 + } +} +``` + +### Movie Detail Response + +```json +{ + "success": true, + "data": { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "year": 1994, + "genres": ["Drama"], + "rating": 9.3, + "directors": ["Frank Darabont"], + "actors": ["Tim Robbins", "Morgan Freeman"], + "plot": "Two imprisoned men bond over a number of years..." + } +} +``` + +## Search Features Demonstrated + +| Feature | Description | +|---------|-------------| +| **BM25 Ranking** | Relevance scoring using BM25 algorithm - the industry standard for text search | +| **Fuzzy Matching** | Handles typos (e.g., "Godfater" finds "Godfather") | +| **Highlighting** | Returns matched text snippets with search terms wrapped in `` tags | + +## Database Schema + +```sql +CREATE TABLE movies ( + id VARCHAR(20) PRIMARY KEY, + title TEXT NOT NULL, + year INTEGER, + genres TEXT[], + rating NUMERIC(3,1), + directors TEXT[], + actors TEXT[], + plot TEXT +); + +-- ParadeDB BM25 search index +CALL paradedb.create_bm25( + index_name => 'movies_search_idx', + table_name => 'movies', + key_field => 'id', + text_fields => paradedb.field('title') || paradedb.field('plot') +); +``` + +## Dataset + +- **Source**: AWS sample-movies dataset (transformed from OpenSearch format) +- **Size**: ~100 movies (curated subset for fast loading) +- **Format**: JSON stored in S3 +- **Fields**: id, title, year, genres, rating, directors, actors, plot + +## Project Structure + +``` +paradedb/sample-movie-search/ +├── README.md # Setup & usage instructions +├── idea.md # This document +├── Makefile # Development commands +├── package.json # CDK dependencies +├── tsconfig.json # TypeScript config +├── cdk.json # CDK config +├── bin/ +│ └── app.ts # CDK app entry point +├── lib/ +│ └── movie-search-stack.ts # CDK stack definition +├── lambda/ +│ ├── package.json # Lambda dependencies +│ ├── search.ts # Search handler +│ ├── movie-detail.ts # Movie detail handler +│ ├── init.ts # Schema/index creation +│ └── seed.ts # Data loading from S3 +├── data/ +│ └── movies.json # Transformed movie dataset +└── web/ + ├── index.html # Main HTML page + ├── style.css # Styling + └── script.js # Search functionality +``` + +## Setup Flow + +### 1. Start LocalStack with ParadeDB Extension + +```bash +localstack extensions install localstack-extension-paradedb +localstack start +``` + +### 2. Deploy Infrastructure + +```bash +cd paradedb/sample-movie-search +npm install +cdklocal bootstrap +cdklocal deploy +``` + +### 3. Initialize Database + +```bash +make init +``` + +This triggers a Lambda that: +- Creates the `movies` table +- Creates the BM25 search index using pg_search + +### 4. Seed Data + +```bash +make seed +``` + +This triggers a Lambda that: +- Reads `movies.json` from S3 +- Inserts all movies into ParadeDB + +### 5. Test the API + +```bash +# Search for movies +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=redemption" + +# Get movie details +curl "https://.execute-api.localhost.localstack.cloud:4566/dev/movies/tt0111161" +``` + +## Makefile Targets + +| Target | Description | +|--------|-------------| +| `make install` | Install all dependencies | +| `make deploy` | Deploy CDK stack to LocalStack | +| `make init` | Create database schema and BM25 index | +| `make seed` | Load movie data from S3 into ParadeDB | +| `make destroy` | Tear down the stack | + +## Web UI Features + +The minimal web UI provides: + +- **Search Box**: Text input with search button +- **Results List**: Movie cards displaying: + - Title + - Year + - Genres (as tags) + - Rating (stars) + - Highlighted plot snippet + +The UI is served as static files and connects to the API Gateway endpoint. + +## ParadeDB Connection + +The Lambda connects to ParadeDB via LocalStack's internal networking: + +``` +PARADEDB_HOST=paradedb.localhost.localstack.cloud +PARADEDB_PORT=5432 +PARADEDB_DATABASE=postgres +PARADEDB_USER=postgres +PARADEDB_PASSWORD=postgres +``` + +## Error Handling + +API returns minimal error responses for security: + +```json +{ + "success": false, + "error": "Search failed" +} +``` + +## What This Demo Shows + +1. **ParadeDB as Elasticsearch Replacement**: Full-text search with BM25 ranking directly in Postgres +2. **LocalStack Extension Integration**: Running ParadeDB alongside AWS services in LocalStack +3. **Serverless Search Architecture**: Lambda + API Gateway pattern for search APIs +4. **Data Pipeline**: S3 → Lambda → ParadeDB ingestion flow +5. **Modern TypeScript Stack**: CDK + Node.js 22 + TypeScript throughout + +## Not Included (By Design) + +- Faceted search / aggregations +- Analytics queries (pg_analytics) +- Automated tests +- Production error handling +- Authentication/authorization +- Caching layer + +## References + +- [ParadeDB Documentation](https://docs.paradedb.com/) +- [LocalStack Extensions](https://docs.localstack.cloud/aws/tooling/extensions/) +- [AWS CDK Local](https://github.com/localstack/aws-cdk-local) +- [WireMock Sample App](../wiremock/sample-app-runner/) (similar pattern) +- [TypeDB Sample App](https://github.com/typedb-osi/typedb-localstack-demo) (similar pattern) diff --git a/paradedb/sample-movie-search/lambda/index.ts b/paradedb/sample-movie-search/lambda/index.ts new file mode 100644 index 00000000..e3d3790b --- /dev/null +++ b/paradedb/sample-movie-search/lambda/index.ts @@ -0,0 +1,269 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { Pool } from "pg"; +import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; + +const pool = new Pool({ + host: process.env.PARADEDB_HOST || "paradedb.localhost.localstack.cloud", + port: parseInt(process.env.PARADEDB_PORT || "5432"), + database: process.env.PARADEDB_DATABASE || "postgres", + user: process.env.PARADEDB_USER || "postgres", + password: process.env.PARADEDB_PASSWORD || "postgres", +}); + +const s3Client = new S3Client({ + endpoint: "http://s3.localhost.localstack.cloud:4566", + region: "us-east-1", + forcePathStyle: true, + credentials: { + accessKeyId: "test", + secretAccessKey: "test", + }, +}); + +const DATA_BUCKET = process.env.DATA_BUCKET || "movie-search-data"; + +function successResponse(data: unknown): APIGatewayProxyResult { + return { + statusCode: 200, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + body: JSON.stringify({ success: true, data }), + }; +} + +function errorResponse( + statusCode: number, + message: string +): APIGatewayProxyResult { + return { + statusCode, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + body: JSON.stringify({ success: false, error: message }), + }; +} + +export async function searchHandler( + event: APIGatewayProxyEvent +): Promise { + try { + const query = event.queryStringParameters?.q; + const limit = parseInt(event.queryStringParameters?.limit || "10"); + const offset = parseInt(event.queryStringParameters?.offset || "0"); + + if (!query) { + return errorResponse(400, "Query parameter 'q' is required"); + } + + console.log(`Searching for: ${query} (limit: ${limit}, offset: ${offset})`); + + const searchQuery = ` + SELECT + id, + title, + year, + genres, + rating, + directors, + actors, + pdb.snippet(plot, start_tag => '', end_tag => '') as highlight, + pdb.score(id) as score + FROM movies + WHERE title ||| $1::pdb.fuzzy(1) OR plot ||| $1::pdb.fuzzy(1) + ORDER BY score DESC + LIMIT $2 OFFSET $3 + `; + + const result = await pool.query(searchQuery, [query, limit, offset]); + + const countQuery = ` + SELECT COUNT(*) as total + FROM movies + WHERE title ||| $1::pdb.fuzzy(1) OR plot ||| $1::pdb.fuzzy(1) + `; + const countResult = await pool.query(countQuery, [query]); + const total = parseInt(countResult.rows[0].total); + + return successResponse({ + results: result.rows.map((row) => ({ + id: row.id, + title: row.title, + year: row.year, + genres: row.genres, + rating: parseFloat(row.rating), + directors: row.directors, + actors: row.actors, + highlight: row.highlight, + })), + total, + limit, + offset, + }); + } catch (error) { + console.error("Search error:", error); + return errorResponse(500, "Search failed"); + } +} + +export async function movieDetailHandler( + event: APIGatewayProxyEvent +): Promise { + try { + const movieId = event.pathParameters?.id; + + if (!movieId) { + return errorResponse(400, "Movie ID is required"); + } + + console.log(`Fetching movie: ${movieId}`); + + const query = ` + SELECT id, title, year, genres, rating, directors, actors, plot + FROM movies + WHERE id = $1 + `; + + const result = await pool.query(query, [movieId]); + + if (result.rows.length === 0) { + return errorResponse(404, "Movie not found"); + } + + const movie = result.rows[0]; + return successResponse({ + id: movie.id, + title: movie.title, + year: movie.year, + genres: movie.genres, + rating: parseFloat(movie.rating), + directors: movie.directors, + actors: movie.actors, + plot: movie.plot, + }); + } catch (error) { + console.error("Movie detail error:", error); + return errorResponse(500, "Failed to fetch movie"); + } +} + +export async function initHandler(): Promise { + const client = await pool.connect(); + + try { + console.log("Initializing database schema..."); + + await client.query(` + CREATE TABLE IF NOT EXISTS movies ( + id VARCHAR(20) PRIMARY KEY, + title TEXT NOT NULL, + year INTEGER, + genres TEXT[], + rating NUMERIC(3,1), + directors TEXT[], + actors TEXT[], + plot TEXT + ) + `); + + console.log("Movies table created"); + + const indexCheck = await client.query(` + SELECT 1 FROM pg_indexes WHERE indexname = 'movies_search_idx' + `); + + if (indexCheck.rows.length === 0) { + console.log("Creating BM25 search index..."); + + await client.query(` + CREATE INDEX movies_search_idx ON movies + USING bm25 (id, title, plot) + WITH (key_field = 'id') + `); + + console.log("BM25 index created"); + } else { + console.log("BM25 index already exists"); + } + + return successResponse({ + message: "Database initialized successfully", + table: "movies", + index: "movies_search_idx", + }); + } catch (error) { + console.error("Init error:", error); + return errorResponse(500, "Initialization failed"); + } finally { + client.release(); + } +} + +export async function seedHandler(): Promise { + const client = await pool.connect(); + + try { + console.log(`Loading movie data from S3 bucket: ${DATA_BUCKET}`); + + const command = new GetObjectCommand({ + Bucket: DATA_BUCKET, + Key: "movies.json", + }); + + const response = await s3Client.send(command); + const bodyString = await response.Body?.transformToString(); + + if (!bodyString) { + return errorResponse(500, "Failed to read movie data from S3"); + } + + const movies = JSON.parse(bodyString); + console.log(`Loaded ${movies.length} movies from S3`); + + await client.query("DELETE FROM movies"); + + let inserted = 0; + for (const movie of movies) { + await client.query( + ` + INSERT INTO movies (id, title, year, genres, rating, directors, actors, plot) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + year = EXCLUDED.year, + genres = EXCLUDED.genres, + rating = EXCLUDED.rating, + directors = EXCLUDED.directors, + actors = EXCLUDED.actors, + plot = EXCLUDED.plot + `, + [ + movie.id, + movie.title, + movie.year, + movie.genres, + movie.rating, + movie.directors, + movie.actors, + movie.plot, + ] + ); + inserted++; + } + + console.log(`Inserted ${inserted} movies`); + + return successResponse({ + message: "Data seeded successfully", + count: inserted, + }); + } catch (error) { + console.error("Seed error:", error); + return errorResponse(500, "Seeding failed"); + } finally { + client.release(); + } +} diff --git a/paradedb/sample-movie-search/lambda/package-lock.json b/paradedb/sample-movie-search/lambda/package-lock.json new file mode 100644 index 00000000..db98c8db --- /dev/null +++ b/paradedb/sample-movie-search/lambda/package-lock.json @@ -0,0 +1,2357 @@ +{ + "name": "movie-search-lambda", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "movie-search-lambda", + "version": "1.0.0", + "dependencies": { + "@aws-sdk/client-s3": "^3.700.0", + "pg": "^8.13.0" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.145", + "@types/node": "^22.0.0", + "@types/pg": "^8.11.10", + "esbuild": "^0.24.0", + "typescript": "~5.6.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.982.0.tgz", + "integrity": "sha512-k0ANYAtPiON9BwLXcDgJXkmmCAGEuSk2pZOvrMej2kNhs3xTXoPshIUR5UMCD9apYiWtXJJfXMZSgaME+iWNaQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", + "@aws-sdk/middleware-expect-continue": "^3.972.3", + "@aws-sdk/middleware-flexible-checksums": "^3.972.4", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-location-constraint": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/middleware-ssec": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/signature-v4-multi-region": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/eventstream-serde-config-resolver": "^4.3.8", + "@smithy/eventstream-serde-node": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-blob-browser": "^4.2.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/hash-stream-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/md5-js": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", + "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", + "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", + "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", + "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", + "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", + "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-login": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", + "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", + "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-ini": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", + "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", + "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.982.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/token-providers": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", + "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.3.tgz", + "integrity": "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.3.tgz", + "integrity": "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", + "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/crc64-nvme": "3.972.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.3.tgz", + "integrity": "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", + "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-arn-parser": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz", + "integrity": "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", + "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", + "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.982.0.tgz", + "integrity": "sha512-AWqjMAH848aNwnLCtIKM3WO00eHuUoYVfQMP4ccrUHhnEduGOusVgdHQ5mLNQZZNZzREuBwnPPhIP55cy0gFSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", + "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", + "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", + "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", + "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.11", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", + "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", + "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", + "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", + "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", + "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", + "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", + "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", + "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", + "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.22.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", + "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", + "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", + "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.22.1", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", + "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", + "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", + "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.160", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.160.tgz", + "integrity": "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", + "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.11.0", + "pg-pool": "^3.11.0", + "pg-protocol": "^1.11.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", + "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/paradedb/sample-movie-search/lambda/package.json b/paradedb/sample-movie-search/lambda/package.json new file mode 100644 index 00000000..2931d451 --- /dev/null +++ b/paradedb/sample-movie-search/lambda/package.json @@ -0,0 +1,20 @@ +{ + "name": "movie-search-lambda", + "version": "1.0.0", + "description": "Lambda functions for ParadeDB movie search", + "main": "index.js", + "scripts": { + "build": "esbuild index.ts --bundle --platform=node --target=node22 --outfile=dist/index.js" + }, + "dependencies": { + "pg": "^8.13.0", + "@aws-sdk/client-s3": "^3.700.0" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.145", + "@types/node": "^22.0.0", + "@types/pg": "^8.11.10", + "esbuild": "^0.24.0", + "typescript": "~5.6.0" + } +} diff --git a/paradedb/sample-movie-search/lib/movie-search-stack.ts b/paradedb/sample-movie-search/lib/movie-search-stack.ts new file mode 100644 index 00000000..9d42e212 --- /dev/null +++ b/paradedb/sample-movie-search/lib/movie-search-stack.ts @@ -0,0 +1,181 @@ +import * as cdk from "aws-cdk-lib"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as apigateway from "aws-cdk-lib/aws-apigateway"; +import * as s3 from "aws-cdk-lib/aws-s3"; +import * as s3deploy from "aws-cdk-lib/aws-s3-deployment"; +import * as path from "path"; +import { Construct } from "constructs"; + +export class MovieSearchStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const dataBucket = new s3.Bucket(this, "MovieDataBucket", { + bucketName: "movie-search-data", + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + new s3deploy.BucketDeployment(this, "DeployMovieData", { + sources: [s3deploy.Source.asset(path.join(__dirname, "../data"))], + destinationBucket: dataBucket, + }); + + const paradeDbEnv = { + PARADEDB_HOST: "paradedb.localhost.localstack.cloud", + PARADEDB_PORT: "4566", + PARADEDB_DATABASE: "mydatabase", + PARADEDB_USER: "myuser", + PARADEDB_PASSWORD: "mypassword", + DATA_BUCKET: dataBucket.bucketName, + }; + + const lambdaDir = path.join(__dirname, "../lambda"); + const getLambdaCode = () => { + return lambda.Code.fromAsset(lambdaDir, { + bundling: { + image: lambda.Runtime.NODEJS_22_X.bundlingImage, + command: [ + "bash", + "-c", + [ + "npm install --prefix /asset-input", + "npx esbuild /asset-input/index.ts --bundle --platform=node --target=node22 --outfile=/asset-output/index.js", + ].join(" && "), + ], + local: { + tryBundle(outputDir: string) { + const { spawnSync } = require("child_process"); + try { + const npmResult = spawnSync("npm", ["install"], { + cwd: lambdaDir, + stdio: "inherit", + }); + if (npmResult.status !== 0) return false; + + const esbuildResult = spawnSync( + "npx", + [ + "esbuild", + "index.ts", + "--bundle", + "--platform=node", + "--target=node22", + `--outfile=${outputDir}/index.js`, + ], + { cwd: lambdaDir, stdio: "inherit" } + ); + return esbuildResult.status === 0; + } catch { + return false; + } + }, + }, + }, + }); + }; + + const searchHandler = new lambda.Function(this, "SearchHandler", { + runtime: lambda.Runtime.NODEJS_22_X, + handler: "index.searchHandler", + code: getLambdaCode(), + environment: paradeDbEnv, + timeout: cdk.Duration.seconds(30), + memorySize: 256, + }); + + const movieDetailHandler = new lambda.Function(this, "MovieDetailHandler", { + runtime: lambda.Runtime.NODEJS_22_X, + handler: "index.movieDetailHandler", + code: getLambdaCode(), + environment: paradeDbEnv, + timeout: cdk.Duration.seconds(30), + memorySize: 256, + }); + + const initHandler = new lambda.Function(this, "InitHandler", { + runtime: lambda.Runtime.NODEJS_22_X, + handler: "index.initHandler", + code: getLambdaCode(), + environment: paradeDbEnv, + timeout: cdk.Duration.seconds(60), + memorySize: 256, + }); + + const seedHandler = new lambda.Function(this, "SeedHandler", { + runtime: lambda.Runtime.NODEJS_22_X, + handler: "index.seedHandler", + code: getLambdaCode(), + environment: paradeDbEnv, + timeout: cdk.Duration.seconds(120), + memorySize: 512, + }); + + dataBucket.grantRead(seedHandler); + + const api = new apigateway.RestApi(this, "MovieSearchApi", { + restApiName: "Movie Search API", + description: "API for searching movies using ParadeDB", + deployOptions: { + stageName: "dev", + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + }, + }); + + // Custom ID for consistent API Gateway URL across deployments + cdk.Tags.of(api).add("_custom_id_", "movie-search-api"); + + const searchResource = api.root.addResource("search"); + searchResource.addMethod( + "GET", + new apigateway.LambdaIntegration(searchHandler) + ); + + const moviesResource = api.root.addResource("movies"); + const movieIdResource = moviesResource.addResource("{id}"); + movieIdResource.addMethod( + "GET", + new apigateway.LambdaIntegration(movieDetailHandler) + ); + + const adminResource = api.root.addResource("admin"); + const initResource = adminResource.addResource("init"); + initResource.addMethod( + "POST", + new apigateway.LambdaIntegration(initHandler) + ); + + const seedResource = adminResource.addResource("seed"); + seedResource.addMethod( + "POST", + new apigateway.LambdaIntegration(seedHandler) + ); + + new cdk.CfnOutput(this, "ApiEndpoint", { + value: api.url, + }); + + new cdk.CfnOutput(this, "SearchEndpoint", { + value: `${api.url}search`, + }); + + new cdk.CfnOutput(this, "MoviesEndpoint", { + value: `${api.url}movies/{id}`, + }); + + new cdk.CfnOutput(this, "InitEndpoint", { + value: `${api.url}admin/init`, + }); + + new cdk.CfnOutput(this, "SeedEndpoint", { + value: `${api.url}admin/seed`, + }); + + new cdk.CfnOutput(this, "DataBucketName", { + value: dataBucket.bucketName, + }); + } +} diff --git a/paradedb/sample-movie-search/package-lock.json b/paradedb/sample-movie-search/package-lock.json new file mode 100644 index 00000000..5d0d0f1b --- /dev/null +++ b/paradedb/sample-movie-search/package-lock.json @@ -0,0 +1,511 @@ +{ + "name": "sample-movie-search", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sample-movie-search", + "version": "1.0.0", + "bin": { + "app": "bin/app.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "aws-cdk": "^2.170.0", + "aws-cdk-lib": "^2.170.0", + "constructs": "^10.4.2", + "typescript": "~5.6.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.263", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.263.tgz", + "integrity": "sha512-X9JvcJhYcb7PHs8R7m4zMablO5C9PGb/hYfLnxds9h/rKJu6l7MiXE/SabCibuehxPnuO/vk+sVVJiUWrccarQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "48.20.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-48.20.0.tgz", + "integrity": "sha512-+eeiav9LY4wbF/EFuCt/vfvi/Zoxo8bf94PW5clbMraChEliq83w4TbRVy0jB9jE0v1ooFTtIjSQkowSPkfISg==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.2" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/node": { + "version": "22.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", + "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/aws-cdk": { + "version": "2.1104.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1104.0.tgz", + "integrity": "sha512-TGIK2Mpfqi0BA6Np9aJz0d5HAvTxWd17FrwtXlJuwqdQbR9R/IRqsabF6xRAuhFTz7/YrrHHU9H4VK/Xfnh7Vg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.237.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.237.1.tgz", + "integrity": "sha512-RH8mWHLBtc14stkeUF0gFLaWdS5iS2AHUHnoT5B5LLZfEkYP9G43LjJs0AMmpdlQ5/ZQVvCZ+VB83Q9vLxILRw==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.263", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^48.20.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.3", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.7.3", + "table": "^6.9.0", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "dev": true, + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constructs": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.5.tgz", + "integrity": "sha512-fOoP70YLevMZr5avJHx2DU3LNYmC6wM8OwdrNewMZou1kZnPGOeVzBrRjZNgFDHUlulYUjkpFRSpTE3D+n+ZSg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/paradedb/sample-movie-search/package.json b/paradedb/sample-movie-search/package.json new file mode 100644 index 00000000..7c4b64e0 --- /dev/null +++ b/paradedb/sample-movie-search/package.json @@ -0,0 +1,20 @@ +{ + "name": "sample-movie-search", + "version": "1.0.0", + "description": "A CDK application demonstrating ParadeDB full-text search with LocalStack", + "bin": { + "app": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "aws-cdk": "^2.170.0", + "aws-cdk-lib": "^2.170.0", + "constructs": "^10.4.2", + "typescript": "~5.6.0" + } +} diff --git a/paradedb/sample-movie-search/tsconfig.json b/paradedb/sample-movie-search/tsconfig.json new file mode 100644 index 00000000..cda2d940 --- /dev/null +++ b/paradedb/sample-movie-search/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "outDir": "dist", + "rootDir": ".", + "skipLibCheck": true + }, + "include": ["bin/**/*", "lib/**/*"], + "exclude": ["node_modules", "cdk.out", "lambda"] +} diff --git a/paradedb/sample-movie-search/web/index.html b/paradedb/sample-movie-search/web/index.html new file mode 100644 index 00000000..4445514a --- /dev/null +++ b/paradedb/sample-movie-search/web/index.html @@ -0,0 +1,55 @@ + + + + + + Movie Search - ParadeDB Demo + + + +
+
+

Movie Search

+

Powered by ParadeDB Full-Text Search

+
+ + + + + + + + + +
+ + +
+ + + + + + diff --git a/paradedb/sample-movie-search/web/style.css b/paradedb/sample-movie-search/web/style.css new file mode 100644 index 00000000..dca7b899 --- /dev/null +++ b/paradedb/sample-movie-search/web/style.css @@ -0,0 +1,277 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + background-color: #f5f5f5; + color: #333; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding: 2rem 1rem; + flex: 1; +} + +header { + text-align: center; + margin-bottom: 2rem; +} + +header h1 { + font-size: 2.5rem; + color: #2c3e50; + margin-bottom: 0.5rem; +} + +.subtitle { + color: #7f8c8d; + font-size: 1rem; +} + +.search-box { + display: flex; + gap: 0.5rem; + margin-bottom: 1.5rem; +} + +#search-input { + flex: 1; + padding: 1rem; + font-size: 1rem; + border: 2px solid #ddd; + border-radius: 8px; + outline: none; + transition: border-color 0.2s; +} + +#search-input:focus { + border-color: #3498db; +} + +#search-btn { + padding: 1rem 2rem; + font-size: 1rem; + background-color: #3498db; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.2s; +} + +#search-btn:hover { + background-color: #2980b9; +} + +.loading { + text-align: center; + padding: 2rem; + color: #7f8c8d; +} + +.spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid #ddd; + border-top-color: #3498db; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-right: 0.5rem; + vertical-align: middle; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.hidden { + display: none !important; +} + +.error { + background-color: #fee; + color: #c00; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.results-info { + color: #7f8c8d; + margin-bottom: 1rem; + font-size: 0.9rem; +} + +.results { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.movie-card { + background: white; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.2s; +} + +.movie-card:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.movie-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.75rem; +} + +.movie-title { + font-size: 1.25rem; + font-weight: 600; + color: #2c3e50; +} + +.movie-year { + color: #7f8c8d; + font-size: 0.9rem; + margin-left: 0.5rem; +} + +.movie-rating { + background-color: #f1c40f; + color: #2c3e50; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-weight: 600; + font-size: 0.9rem; + white-space: nowrap; +} + +.movie-genres { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 0.75rem; +} + +.genre-tag { + background-color: #ecf0f1; + color: #7f8c8d; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.8rem; +} + +.movie-highlight { + color: #555; + line-height: 1.6; + font-size: 0.95rem; +} + +.movie-highlight mark { + background-color: #ffeaa7; + padding: 0 2px; + border-radius: 2px; +} + +.movie-meta { + margin-top: 0.75rem; + font-size: 0.85rem; + color: #95a5a6; +} + +.movie-meta span { + margin-right: 1rem; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin-top: 2rem; + padding: 1rem; +} + +.pagination button { + padding: 0.75rem 1.5rem; + font-size: 0.9rem; + background-color: #ecf0f1; + color: #2c3e50; + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.2s; +} + +.pagination button:hover:not(:disabled) { + background-color: #bdc3c7; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +#page-info { + color: #7f8c8d; +} + +footer { + text-align: center; + padding: 1.5rem; + color: #95a5a6; + font-size: 0.9rem; +} + +footer a { + color: #3498db; + text-decoration: none; +} + +footer a:hover { + text-decoration: underline; +} + +/* Empty state */ +.empty-state { + text-align: center; + padding: 3rem; + color: #95a5a6; +} + +.empty-state h3 { + margin-bottom: 0.5rem; + color: #7f8c8d; +} + +/* Responsive */ +@media (max-width: 600px) { + .search-box { + flex-direction: column; + } + + #search-btn { + width: 100%; + } + + .movie-header { + flex-direction: column; + gap: 0.5rem; + } + + .movie-rating { + align-self: flex-start; + } +} From c3e78b819db6e623583747ac40c89ca6d69aa5bf Mon Sep 17 00:00:00 2001 From: HarshCasper Date: Wed, 4 Feb 2026 20:22:07 +0530 Subject: [PATCH 2/3] second iteration --- paradedb/sample-movie-search/.gitignore | 3 + paradedb/sample-movie-search/Makefile | 36 +- paradedb/sample-movie-search/README.md | 110 ++- paradedb/sample-movie-search/data/movies.json | 912 ------------------ paradedb/sample-movie-search/lambda/index.ts | 128 ++- .../lib/movie-search-stack.ts | 4 +- paradedb/sample-movie-search/web/style.css | 45 + 7 files changed, 236 insertions(+), 1002 deletions(-) delete mode 100644 paradedb/sample-movie-search/data/movies.json diff --git a/paradedb/sample-movie-search/.gitignore b/paradedb/sample-movie-search/.gitignore index 9a2a24d6..074cdab2 100644 --- a/paradedb/sample-movie-search/.gitignore +++ b/paradedb/sample-movie-search/.gitignore @@ -27,3 +27,6 @@ npm-debug.log* # Local env .env .env.local + +# Data +data/ \ No newline at end of file diff --git a/paradedb/sample-movie-search/Makefile b/paradedb/sample-movie-search/Makefile index c14140d5..2f657387 100644 --- a/paradedb/sample-movie-search/Makefile +++ b/paradedb/sample-movie-search/Makefile @@ -1,19 +1,23 @@ -.PHONY: install deploy init seed destroy clean help web-ui test-search get-api-url +.PHONY: install deploy init seed destroy clean help web-ui test-search get-api-url download-data + +DATASET_URL := https://docs.aws.amazon.com/opensearch-service/latest/developerguide/samples/sample-movies.zip +DATA_DIR := data help: @echo "ParadeDB Movie Search Sample App" @echo "" @echo "Usage:" - @echo " make install - Install all dependencies" - @echo " make deploy - Deploy CDK stack to LocalStack" - @echo " make init - Initialize database schema and BM25 index" - @echo " make seed - Load movie data from S3 into ParadeDB" - @echo " make web-ui - Run the Web UI on localhost port 3000" - @echo " make destroy - Tear down the stack" - @echo " make clean - Remove build artifacts" + @echo " make install - Install all dependencies" + @echo " make download-data - Download AWS sample movies dataset" + @echo " make deploy - Deploy CDK stack to LocalStack" + @echo " make init - Initialize database schema and BM25 index" + @echo " make seed - Load movie data from S3 into ParadeDB" + @echo " make web-ui - Run the Web UI on localhost port 3000" + @echo " make destroy - Tear down the stack" + @echo " make clean - Remove build artifacts" @echo "" @echo "Quick start:" - @echo " make install && make deploy && make init && make seed && make web-ui" + @echo " make install && make download-data && make deploy && make init && make seed && make web-ui" install: @echo "Installing CDK dependencies..." @@ -22,6 +26,18 @@ install: cd lambda && npm install @echo "Done!" +download-data: + @echo "Downloading AWS sample movies dataset..." + @mkdir -p $(DATA_DIR) + @curl -sL $(DATASET_URL) -o $(DATA_DIR)/sample-movies.zip + @echo "Extracting dataset..." + @unzip -o $(DATA_DIR)/sample-movies.zip -d $(DATA_DIR)/ + @echo "Pre-processing bulk file (removing index instructions)..." + @grep -v '^{ "index"' $(DATA_DIR)/sample-movies.bulk > $(DATA_DIR)/movies.bulk + @rm -rf $(DATA_DIR)/sample-movies.zip $(DATA_DIR)/sample-movies.bulk $(DATA_DIR)/__MACOSX + @echo "Dataset ready: $(DATA_DIR)/movies.bulk" + @wc -l $(DATA_DIR)/movies.bulk | awk '{print "Total movies: " $$1}' + deploy: @echo "Deploying MovieSearchStack to LocalStack..." cdklocal bootstrap @@ -74,7 +90,7 @@ destroy: @echo "Stack destroyed!" clean: - rm -rf node_modules lambda/node_modules cdk.out dist + rm -rf node_modules lambda/node_modules cdk.out dist data/movies.bulk @echo "Cleaned!" get-api-url: diff --git a/paradedb/sample-movie-search/README.md b/paradedb/sample-movie-search/README.md index b25f6630..8b3055ec 100644 --- a/paradedb/sample-movie-search/README.md +++ b/paradedb/sample-movie-search/README.md @@ -11,6 +11,16 @@ This sample app deploys a serverless movie search application using: - **Amazon S3** - Stores movie dataset - **ParadeDB** - Full-text search engine (runs as LocalStack extension) +### Dataset + +Uses the official [AWS OpenSearch sample movies dataset](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/samples/sample-movies.zip) containing **5,000 movies** with metadata including: + +- Title, year, genres, rating +- Directors and actors +- Plot descriptions +- Movie poster images +- Runtime duration + ### Features Demonstrated | Feature | Description | @@ -18,6 +28,7 @@ This sample app deploys a serverless movie search application using: | **BM25 Ranking** | Industry-standard relevance scoring | | **Fuzzy Matching** | Handles typos (e.g., "Godfater" finds "Godfather") | | **Highlighting** | Returns matched text with highlighted terms | +| **Movie Posters** | Rich UI with movie poster images | ### API Endpoints @@ -48,19 +59,15 @@ localstack extensions install localstack-extension-paradedb localstack start ``` -### 2. Install Dependencies +### 2. Install Dependencies and Download Dataset ```bash cd paradedb/sample-movie-search make install +make download-data ``` -Or manually: - -```bash -npm install -cd lambda && npm install -``` +The `download-data` target downloads the AWS sample movies dataset (~5000 movies) and preprocesses it for ParadeDB ingestion. ### 3. Deploy the Stack @@ -110,19 +117,19 @@ make seed ```bash # Basic search -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=redemption" +curl "https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/search?q=redemption" # With pagination -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=dark%20knight&limit=5&offset=0" +curl "https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/search?q=dark%20knight&limit=5&offset=0" # Fuzzy search (handles typos) -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=godfater" +curl "https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/search?q=godfater" ``` ### Get Movie Details ```bash -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/movies/tt0111161" +curl "https://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev/movies/tt0111161" ``` ### Example Response @@ -131,60 +138,75 @@ curl "https://.execute-api.localhost.localstack.cloud:4566/dev/movies/tt { "success": true, "data": { - "results": [ - { - "id": "tt0111161", - "title": "The Shawshank Redemption", - "year": 1994, - "genres": ["Drama"], - "rating": 9.3, - "directors": ["Frank Darabont"], - "actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton"], - "highlight": "...finding solace and eventual redemption through acts of common decency." - } + "id": "tt0111161", + "title": "The Shawshank Redemption", + "year": 1994, + "genres": [ + "Crime", + "Drama" ], - "total": 1, - "limit": 10, - "offset": 0 + "rating": 9.3, + "directors": [ + "Frank Darabont" + ], + "actors": [ + "Tim Robbins", + "Morgan Freeman", + "Bob Gunton" + ], + "plot": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.", + "image_url": "https://m.media-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE@._V1_SX400_.jpg", + "release_date": "1994-09-10T00:00:00.000Z", + "rank": 80, + "running_time_secs": 8520 } } ``` ## Web UI -A minimal web UI is included in the `web/` directory. To use it: +A web UI with movie posters is included in the `web/` directory. -1. Open `web/index.html` in a browser -2. Set the API URL by opening the browser console and running: +### Quick Start -```javascript -setApiUrl('https://.execute-api.localhost.localstack.cloud:4566/dev') +```bash +make web-ui ``` -3. Start searching! +This starts a local web server at http://localhost:3000. The UI automatically connects to the API Gateway at `http://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev`. + +### Features + +- Movie poster images from Amazon +- Runtime display (e.g., "2h 22m") +- Genre tags +- Director and cast information +- Search result highlighting +- Pagination ## How It Works -1. **Deployment**: CDK creates Lambda functions, API Gateway, and S3 bucket with movie data +1. **Dataset Preparation**: Download and preprocess the AWS OpenSearch sample movies dataset + +2. **Deployment**: CDK creates Lambda functions, API Gateway, and S3 bucket with movie data (bulk format) -2. **Initialization**: The init Lambda creates the movies table and ParadeDB BM25 index: +3. **Initialization**: The init Lambda creates the movies table and ParadeDB BM25 index: ```sql - CALL paradedb.create_bm25( - index_name => 'movies_search_idx', - table_name => 'movies', - key_field => 'id', - text_fields => paradedb.field('title') || paradedb.field('plot') - ); + CREATE INDEX movies_search_idx ON movies + USING bm25 (id, title, plot) + WITH (key_field = 'id'); ``` -3. **Data Loading**: The seed Lambda reads `movies.json` from S3 and inserts into ParadeDB +4. **Data Loading**: The seed Lambda reads `movies.bulk` from S3 (newline-delimited JSON) and inserts 5000 movies into ParadeDB -4. **Search**: Queries use ParadeDB's BM25 search with fuzzy matching: +5. **Search**: Queries use ParadeDB's BM25 search with fuzzy matching: ```sql - SELECT *, paradedb.snippet(plot) as highlight + SELECT id, title, year, genres, rating, directors, actors, image_url, running_time_secs, + pdb.snippet(plot, start_tag => '', end_tag => '') as highlight, + pdb.score(id) as score FROM movies - WHERE id @@@ paradedb.parse('title:query~1 OR plot:query~1') - ORDER BY paradedb.score(id) DESC + WHERE title ||| $1::pdb.fuzzy(1) OR plot ||| $1::pdb.fuzzy(1) + ORDER BY score DESC ``` ## References diff --git a/paradedb/sample-movie-search/data/movies.json b/paradedb/sample-movie-search/data/movies.json deleted file mode 100644 index 3eea5418..00000000 --- a/paradedb/sample-movie-search/data/movies.json +++ /dev/null @@ -1,912 +0,0 @@ -[ - { - "id": "tt0111161", - "title": "The Shawshank Redemption", - "year": 1994, - "genres": ["Drama"], - "rating": 9.3, - "directors": ["Frank Darabont"], - "actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton"], - "plot": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency." - }, - { - "id": "tt0068646", - "title": "The Godfather", - "year": 1972, - "genres": ["Crime", "Drama"], - "rating": 9.2, - "directors": ["Francis Ford Coppola"], - "actors": ["Marlon Brando", "Al Pacino", "James Caan"], - "plot": "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant youngest son." - }, - { - "id": "tt0468569", - "title": "The Dark Knight", - "year": 2008, - "genres": ["Action", "Crime", "Drama"], - "rating": 9.0, - "directors": ["Christopher Nolan"], - "actors": ["Christian Bale", "Heath Ledger", "Aaron Eckhart"], - "plot": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice." - }, - { - "id": "tt0071562", - "title": "The Godfather Part II", - "year": 1974, - "genres": ["Crime", "Drama"], - "rating": 9.0, - "directors": ["Francis Ford Coppola"], - "actors": ["Al Pacino", "Robert De Niro", "Robert Duvall"], - "plot": "The early life and career of Vito Corleone in 1920s New York City is portrayed, while his son, Michael, expands and tightens his grip on the family crime syndicate." - }, - { - "id": "tt0050083", - "title": "12 Angry Men", - "year": 1957, - "genres": ["Crime", "Drama"], - "rating": 9.0, - "directors": ["Sidney Lumet"], - "actors": ["Henry Fonda", "Lee J. Cobb", "Martin Balsam"], - "plot": "A jury holdout attempts to prevent a miscarriage of justice by forcing his colleagues to reconsider the evidence." - }, - { - "id": "tt0108052", - "title": "Schindler's List", - "year": 1993, - "genres": ["Biography", "Drama", "History"], - "rating": 9.0, - "directors": ["Steven Spielberg"], - "actors": ["Liam Neeson", "Ralph Fiennes", "Ben Kingsley"], - "plot": "In German-occupied Poland during World War II, industrialist Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing their persecution by the Nazis." - }, - { - "id": "tt0167260", - "title": "The Lord of the Rings: The Return of the King", - "year": 2003, - "genres": ["Action", "Adventure", "Drama"], - "rating": 9.0, - "directors": ["Peter Jackson"], - "actors": ["Elijah Wood", "Viggo Mortensen", "Ian McKellen"], - "plot": "Gandalf and Aragorn lead the World of Men against Sauron's army to draw his gaze from Frodo and Sam as they approach Mount Doom with the One Ring." - }, - { - "id": "tt0110912", - "title": "Pulp Fiction", - "year": 1994, - "genres": ["Crime", "Drama"], - "rating": 8.9, - "directors": ["Quentin Tarantino"], - "actors": ["John Travolta", "Uma Thurman", "Samuel L. Jackson"], - "plot": "The lives of two mob hitmen, a boxer, a gangster and his wife, and a pair of diner bandits intertwine in four tales of violence and redemption." - }, - { - "id": "tt0120737", - "title": "The Lord of the Rings: The Fellowship of the Ring", - "year": 2001, - "genres": ["Action", "Adventure", "Drama"], - "rating": 8.8, - "directors": ["Peter Jackson"], - "actors": ["Elijah Wood", "Ian McKellen", "Orlando Bloom"], - "plot": "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron." - }, - { - "id": "tt0137523", - "title": "Fight Club", - "year": 1999, - "genres": ["Drama"], - "rating": 8.8, - "directors": ["David Fincher"], - "actors": ["Brad Pitt", "Edward Norton", "Meat Loaf"], - "plot": "An insomniac office worker and a devil-may-care soapmaker form an underground fight club that evolves into something much, much more." - }, - { - "id": "tt0109830", - "title": "Forrest Gump", - "year": 1994, - "genres": ["Drama", "Romance"], - "rating": 8.8, - "directors": ["Robert Zemeckis"], - "actors": ["Tom Hanks", "Robin Wright", "Gary Sinise"], - "plot": "The presidencies of Kennedy and Johnson, the Vietnam War, the Watergate scandal and other historical events unfold from the perspective of an Alabama man with an IQ of 75." - }, - { - "id": "tt0167261", - "title": "The Lord of the Rings: The Two Towers", - "year": 2002, - "genres": ["Action", "Adventure", "Drama"], - "rating": 8.8, - "directors": ["Peter Jackson"], - "actors": ["Elijah Wood", "Ian McKellen", "Viggo Mortensen"], - "plot": "While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard." - }, - { - "id": "tt1375666", - "title": "Inception", - "year": 2010, - "genres": ["Action", "Adventure", "Sci-Fi"], - "rating": 8.8, - "directors": ["Christopher Nolan"], - "actors": ["Leonardo DiCaprio", "Joseph Gordon-Levitt", "Elliot Page"], - "plot": "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O." - }, - { - "id": "tt0080684", - "title": "Star Wars: Episode V - The Empire Strikes Back", - "year": 1980, - "genres": ["Action", "Adventure", "Fantasy"], - "rating": 8.7, - "directors": ["Irvin Kershner"], - "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], - "plot": "After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda, while his friends are pursued by Darth Vader." - }, - { - "id": "tt0133093", - "title": "The Matrix", - "year": 1999, - "genres": ["Action", "Sci-Fi"], - "rating": 8.7, - "directors": ["Lana Wachowski", "Lilly Wachowski"], - "actors": ["Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss"], - "plot": "When a beautiful stranger leads computer hacker Neo to a forbidding underworld, he discovers the shocking truth--the life he knows is the elaborate deception of an evil cyber-intelligence." - }, - { - "id": "tt0099685", - "title": "Goodfellas", - "year": 1990, - "genres": ["Biography", "Crime", "Drama"], - "rating": 8.7, - "directors": ["Martin Scorsese"], - "actors": ["Robert De Niro", "Ray Liotta", "Joe Pesci"], - "plot": "The story of Henry Hill and his life in the mob, covering his relationship with his wife Karen Hill and his mob partners Jimmy Conway and Tommy DeVito." - }, - { - "id": "tt0073486", - "title": "One Flew Over the Cuckoo's Nest", - "year": 1975, - "genres": ["Drama"], - "rating": 8.7, - "directors": ["Milos Forman"], - "actors": ["Jack Nicholson", "Louise Fletcher", "Will Sampson"], - "plot": "A criminal pleads insanity and is admitted to a mental institution, where he rebels against the oppressive nurse and rallies up the scared patients." - }, - { - "id": "tt0114369", - "title": "Se7en", - "year": 1995, - "genres": ["Crime", "Drama", "Mystery"], - "rating": 8.6, - "directors": ["David Fincher"], - "actors": ["Morgan Freeman", "Brad Pitt", "Kevin Spacey"], - "plot": "Two detectives, a rookie and a veteran, hunt a serial killer who uses the seven deadly sins as his motives." - }, - { - "id": "tt0038650", - "title": "It's a Wonderful Life", - "year": 1946, - "genres": ["Drama", "Family", "Fantasy"], - "rating": 8.6, - "directors": ["Frank Capra"], - "actors": ["James Stewart", "Donna Reed", "Lionel Barrymore"], - "plot": "An angel is sent from Heaven to help a desperately frustrated businessman by showing him what life would have been like if he had never existed." - }, - { - "id": "tt0102926", - "title": "The Silence of the Lambs", - "year": 1991, - "genres": ["Crime", "Drama", "Thriller"], - "rating": 8.6, - "directors": ["Jonathan Demme"], - "actors": ["Jodie Foster", "Anthony Hopkins", "Lawrence A. Bonney"], - "plot": "A young F.B.I. cadet must receive the help of an incarcerated and manipulative cannibal killer to help catch another serial killer, a madman who skins his victims." - }, - { - "id": "tt0076759", - "title": "Star Wars: Episode IV - A New Hope", - "year": 1977, - "genres": ["Action", "Adventure", "Fantasy"], - "rating": 8.6, - "directors": ["George Lucas"], - "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], - "plot": "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from the Empire's world-destroying battle station." - }, - { - "id": "tt0120689", - "title": "The Green Mile", - "year": 1999, - "genres": ["Crime", "Drama", "Fantasy"], - "rating": 8.6, - "directors": ["Frank Darabont"], - "actors": ["Tom Hanks", "Michael Clarke Duncan", "David Morse"], - "plot": "The lives of guards on Death Row are affected by one of their charges: a black man accused of child murder and rape, yet who has a mysterious gift." - }, - { - "id": "tt0816692", - "title": "Interstellar", - "year": 2014, - "genres": ["Adventure", "Drama", "Sci-Fi"], - "rating": 8.6, - "directors": ["Christopher Nolan"], - "actors": ["Matthew McConaughey", "Anne Hathaway", "Jessica Chastain"], - "plot": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival." - }, - { - "id": "tt0245429", - "title": "Spirited Away", - "year": 2001, - "genres": ["Animation", "Adventure", "Family"], - "rating": 8.6, - "directors": ["Hayao Miyazaki"], - "actors": ["Daveigh Chase", "Suzanne Pleshette", "Miyu Irino"], - "plot": "During her family's move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods, witches, and spirits, and where humans are changed into beasts." - }, - { - "id": "tt0120815", - "title": "Saving Private Ryan", - "year": 1998, - "genres": ["Drama", "War"], - "rating": 8.6, - "directors": ["Steven Spielberg"], - "actors": ["Tom Hanks", "Matt Damon", "Tom Sizemore"], - "plot": "Following the Normandy Landings, a group of U.S. soldiers go behind enemy lines to retrieve a paratrooper whose brothers have been killed in action." - }, - { - "id": "tt0047478", - "title": "Seven Samurai", - "year": 1954, - "genres": ["Action", "Adventure", "Drama"], - "rating": 8.6, - "directors": ["Akira Kurosawa"], - "actors": ["Toshiro Mifune", "Takashi Shimura", "Keiko Tsushima"], - "plot": "A poor village under attack by bandits recruits seven unemployed samurai to help them defend themselves." - }, - { - "id": "tt0103064", - "title": "Terminator 2: Judgment Day", - "year": 1991, - "genres": ["Action", "Sci-Fi"], - "rating": 8.6, - "directors": ["James Cameron"], - "actors": ["Arnold Schwarzenegger", "Linda Hamilton", "Edward Furlong"], - "plot": "A cyborg, identical to the one who failed to kill Sarah Connor, must now protect her teenage son, John Connor, from a more advanced and powerful cyborg." - }, - { - "id": "tt0054215", - "title": "Psycho", - "year": 1960, - "genres": ["Horror", "Mystery", "Thriller"], - "rating": 8.5, - "directors": ["Alfred Hitchcock"], - "actors": ["Anthony Perkins", "Janet Leigh", "Vera Miles"], - "plot": "A Phoenix secretary embezzles forty thousand dollars from her employer's client, goes on the run, and checks into a remote motel run by a young man under the domination of his mother." - }, - { - "id": "tt0027977", - "title": "Modern Times", - "year": 1936, - "genres": ["Comedy", "Drama", "Family"], - "rating": 8.5, - "directors": ["Charles Chaplin"], - "actors": ["Charles Chaplin", "Paulette Goddard", "Henry Bergman"], - "plot": "The Tramp struggles to live in modern industrial society with the help of a young homeless woman." - }, - { - "id": "tt0114814", - "title": "The Usual Suspects", - "year": 1995, - "genres": ["Crime", "Drama", "Mystery"], - "rating": 8.5, - "directors": ["Bryan Singer"], - "actors": ["Kevin Spacey", "Gabriel Byrne", "Chazz Palminteri"], - "plot": "A sole survivor tells of the twisty events leading up to a horrific gun battle on a boat, which began when five criminals met at a seemingly random police lineup." - }, - { - "id": "tt0021749", - "title": "City Lights", - "year": 1931, - "genres": ["Comedy", "Drama", "Romance"], - "rating": 8.5, - "directors": ["Charles Chaplin"], - "actors": ["Charles Chaplin", "Virginia Cherrill", "Florence Lee"], - "plot": "With the aid of a wealthy erratic tippler, a tramp who has fallen in love with a blind flower girl accumulates money to be able to help her with an operation that will restore her sight." - }, - { - "id": "tt0034583", - "title": "Casablanca", - "year": 1942, - "genres": ["Drama", "Romance", "War"], - "rating": 8.5, - "directors": ["Michael Curtiz"], - "actors": ["Humphrey Bogart", "Ingrid Bergman", "Paul Henreid"], - "plot": "A cynical expatriate American cafe owner struggles to decide whether or not to help his former lover and her fugitive husband escape the Nazis in French Morocco." - }, - { - "id": "tt0064116", - "title": "Once Upon a Time in the West", - "year": 1968, - "genres": ["Western"], - "rating": 8.5, - "directors": ["Sergio Leone"], - "actors": ["Henry Fonda", "Charles Bronson", "Claudia Cardinale"], - "plot": "A mysterious stranger with a harmonica joins forces with a notorious desperado to protect a beautiful widow from a ruthless assassin working for the railroad." - }, - { - "id": "tt0082971", - "title": "Raiders of the Lost Ark", - "year": 1981, - "genres": ["Action", "Adventure"], - "rating": 8.4, - "directors": ["Steven Spielberg"], - "actors": ["Harrison Ford", "Karen Allen", "Paul Freeman"], - "plot": "In 1936, archaeologist and adventurer Indiana Jones is hired by the U.S. government to find the Ark of the Covenant before Adolf Hitler's Nazis can obtain its awesome powers." - }, - { - "id": "tt0078788", - "title": "Apocalypse Now", - "year": 1979, - "genres": ["Drama", "Mystery", "War"], - "rating": 8.4, - "directors": ["Francis Ford Coppola"], - "actors": ["Martin Sheen", "Marlon Brando", "Robert Duvall"], - "plot": "A U.S. Army officer serving in Vietnam is tasked with assassinating a renegade Special Forces Colonel who sees himself as a god." - }, - { - "id": "tt0078748", - "title": "Alien", - "year": 1979, - "genres": ["Horror", "Sci-Fi"], - "rating": 8.4, - "directors": ["Ridley Scott"], - "actors": ["Sigourney Weaver", "Tom Skerritt", "John Hurt"], - "plot": "After a space merchant vessel receives an unknown transmission as a distress call, one of the crew is attacked by a mysterious life form and they soon realize that its life cycle has merely begun." - }, - { - "id": "tt0209144", - "title": "Memento", - "year": 2000, - "genres": ["Mystery", "Thriller"], - "rating": 8.4, - "directors": ["Christopher Nolan"], - "actors": ["Guy Pearce", "Carrie-Anne Moss", "Joe Pantoliano"], - "plot": "A man with short-term memory loss attempts to track down his wife's murderer." - }, - { - "id": "tt0090605", - "title": "Aliens", - "year": 1986, - "genres": ["Action", "Adventure", "Sci-Fi"], - "rating": 8.4, - "directors": ["James Cameron"], - "actors": ["Sigourney Weaver", "Michael Biehn", "Carrie Henn"], - "plot": "Ellen Ripley is rescued by a deep salvage team after being in hypersleep for 57 years. The moon that the Nostromo visited has been colonized, but contact is lost." - }, - { - "id": "tt0172495", - "title": "Gladiator", - "year": 2000, - "genres": ["Action", "Adventure", "Drama"], - "rating": 8.5, - "directors": ["Ridley Scott"], - "actors": ["Russell Crowe", "Joaquin Phoenix", "Connie Nielsen"], - "plot": "A former Roman General sets out to exact vengeance against the corrupt emperor who murdered his family and sent him into slavery." - }, - { - "id": "tt0407887", - "title": "The Departed", - "year": 2006, - "genres": ["Crime", "Drama", "Thriller"], - "rating": 8.5, - "directors": ["Martin Scorsese"], - "actors": ["Leonardo DiCaprio", "Matt Damon", "Jack Nicholson"], - "plot": "An undercover cop and a mole in the police attempt to identify each other while infiltrating an Irish gang in South Boston." - }, - { - "id": "tt0482571", - "title": "The Prestige", - "year": 2006, - "genres": ["Drama", "Mystery", "Sci-Fi"], - "rating": 8.5, - "directors": ["Christopher Nolan"], - "actors": ["Christian Bale", "Hugh Jackman", "Scarlett Johansson"], - "plot": "After a tragic accident, two stage magicians engage in a battle to create the ultimate illusion while sacrificing everything they have to outwit each other." - }, - { - "id": "tt0032553", - "title": "The Great Dictator", - "year": 1940, - "genres": ["Comedy", "Drama", "War"], - "rating": 8.4, - "directors": ["Charles Chaplin"], - "actors": ["Charles Chaplin", "Paulette Goddard", "Jack Oakie"], - "plot": "Dictator Adenoid Hynkel tries to expand his empire while a poor Jewish barber tries to avoid persecution from Hynkel's regime." - }, - { - "id": "tt0057012", - "title": "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", - "year": 1964, - "genres": ["Comedy", "War"], - "rating": 8.4, - "directors": ["Stanley Kubrick"], - "actors": ["Peter Sellers", "George C. Scott", "Sterling Hayden"], - "plot": "An insane American general orders a bombing attack on the Soviet Union, triggering a path to nuclear holocaust that a war room full of politicians and generals frantically tries to stop." - }, - { - "id": "tt0095765", - "title": "Cinema Paradiso", - "year": 1988, - "genres": ["Drama", "Romance"], - "rating": 8.5, - "directors": ["Giuseppe Tornatore"], - "actors": ["Philippe Noiret", "Enzo Cannavale", "Antonella Attili"], - "plot": "A filmmaker recalls his childhood when falling in love with the pictures at the cinema of his home village and forms a deep friendship with the cinema's projectionist." - }, - { - "id": "tt0253474", - "title": "The Pianist", - "year": 2002, - "genres": ["Biography", "Drama", "Music"], - "rating": 8.5, - "directors": ["Roman Polanski"], - "actors": ["Adrien Brody", "Thomas Kretschmann", "Frank Finlay"], - "plot": "A Polish Jewish musician struggles to survive the destruction of the Warsaw ghetto of World War II." - }, - { - "id": "tt0110413", - "title": "Leon: The Professional", - "year": 1994, - "genres": ["Action", "Crime", "Drama"], - "rating": 8.5, - "directors": ["Luc Besson"], - "actors": ["Jean Reno", "Gary Oldman", "Natalie Portman"], - "plot": "Mathilda, a 12-year-old girl, is reluctantly taken in by Leon, a professional assassin, after her family is murdered. An unusual relationship forms as she becomes his protegee and learns the assassin's trade." - }, - { - "id": "tt0088763", - "title": "Back to the Future", - "year": 1985, - "genres": ["Adventure", "Comedy", "Sci-Fi"], - "rating": 8.5, - "directors": ["Robert Zemeckis"], - "actors": ["Michael J. Fox", "Christopher Lloyd", "Lea Thompson"], - "plot": "Marty McFly, a 17-year-old high school student, is accidentally sent thirty years into the past in a time-traveling DeLorean invented by his close friend, the eccentric scientist Doc Brown." - }, - { - "id": "tt0056172", - "title": "Lawrence of Arabia", - "year": 1962, - "genres": ["Adventure", "Biography", "Drama"], - "rating": 8.3, - "directors": ["David Lean"], - "actors": ["Peter O'Toole", "Alec Guinness", "Anthony Quinn"], - "plot": "The story of T.E. Lawrence, the English officer who successfully united and led the diverse, often warring, Arab tribes during World War I in order to fight the Turks." - }, - { - "id": "tt0119698", - "title": "Princess Mononoke", - "year": 1997, - "genres": ["Animation", "Action", "Adventure"], - "rating": 8.4, - "directors": ["Hayao Miyazaki"], - "actors": ["Yoji Matsuda", "Yuriko Ishida", "Yuko Tanaka"], - "plot": "On a journey to find the cure for a Tatarigami's curse, Ashitaka finds himself in the middle of a war between the forest gods and Tatara, a mining colony." - }, - { - "id": "tt0056592", - "title": "To Kill a Mockingbird", - "year": 1962, - "genres": ["Crime", "Drama"], - "rating": 8.3, - "directors": ["Robert Mulligan"], - "actors": ["Gregory Peck", "John Megna", "Frank Overton"], - "plot": "Atticus Finch, a widowed lawyer in Depression-era Alabama, defends a black man against an undeserved rape charge, and his children against prejudice." - }, - { - "id": "tt0040522", - "title": "Bicycle Thieves", - "year": 1948, - "genres": ["Drama"], - "rating": 8.3, - "directors": ["Vittorio De Sica"], - "actors": ["Lamberto Maggiorani", "Enzo Staiola", "Lianella Carell"], - "plot": "In post-war Italy, a working-class man's bicycle is stolen. He and his son set out to find it." - }, - { - "id": "tt0042876", - "title": "Sunset Boulevard", - "year": 1950, - "genres": ["Drama", "Film-Noir"], - "rating": 8.4, - "directors": ["Billy Wilder"], - "actors": ["William Holden", "Gloria Swanson", "Erich von Stroheim"], - "plot": "A screenwriter develops a dangerous relationship with a faded film star determined to make a triumphant return." - }, - { - "id": "tt0051201", - "title": "Witness for the Prosecution", - "year": 1957, - "genres": ["Crime", "Drama", "Mystery"], - "rating": 8.4, - "directors": ["Billy Wilder"], - "actors": ["Tyrone Power", "Marlene Dietrich", "Charles Laughton"], - "plot": "A veteran British barrister must defend his client in a murder trial that has surprise after surprise." - }, - { - "id": "tt0081505", - "title": "The Shining", - "year": 1980, - "genres": ["Drama", "Horror"], - "rating": 8.4, - "directors": ["Stanley Kubrick"], - "actors": ["Jack Nicholson", "Shelley Duvall", "Danny Lloyd"], - "plot": "A family heads to an isolated hotel for the winter where a sinister presence influences the father into violence, while his psychic son sees horrific forebodings from both past and future." - }, - { - "id": "tt0044741", - "title": "Ikiru", - "year": 1952, - "genres": ["Drama"], - "rating": 8.3, - "directors": ["Akira Kurosawa"], - "actors": ["Takashi Shimura", "Nobuo Kaneko", "Shin'ichi Himori"], - "plot": "A bureaucrat tries to find meaning in his life after he discovers he has terminal cancer." - }, - { - "id": "tt0087843", - "title": "Once Upon a Time in America", - "year": 1984, - "genres": ["Crime", "Drama"], - "rating": 8.3, - "directors": ["Sergio Leone"], - "actors": ["Robert De Niro", "James Woods", "Elizabeth McGovern"], - "plot": "A former Prohibition-era Jewish gangster returns to the Lower East Side of Manhattan over thirty years later, where he once again must confront the ghosts and regrets of his old life." - }, - { - "id": "tt0405094", - "title": "The Lives of Others", - "year": 2006, - "genres": ["Drama", "Mystery", "Thriller"], - "rating": 8.4, - "directors": ["Florian Henckel von Donnersmarck"], - "actors": ["Ulrich Muhe", "Martina Gedeck", "Sebastian Koch"], - "plot": "In 1984 East Berlin, an idealistic Stasi agent is assigned to monitor a successful playwright and his partner, but he gradually becomes absorbed by their lives." - }, - { - "id": "tt0086190", - "title": "Star Wars: Episode VI - Return of the Jedi", - "year": 1983, - "genres": ["Action", "Adventure", "Fantasy"], - "rating": 8.3, - "directors": ["Richard Marquand"], - "actors": ["Mark Hamill", "Harrison Ford", "Carrie Fisher"], - "plot": "After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star." - }, - { - "id": "tt0086879", - "title": "Amadeus", - "year": 1984, - "genres": ["Biography", "Drama", "Music"], - "rating": 8.3, - "directors": ["Milos Forman"], - "actors": ["F. Murray Abraham", "Tom Hulce", "Elizabeth Berridge"], - "plot": "The life, success and troubles of Wolfgang Amadeus Mozart, as told by Antonio Salieri, the contemporaneous composer who was insanely jealous of Mozart's talent and claimed to have murdered him." - }, - { - "id": "tt0052357", - "title": "Vertigo", - "year": 1958, - "genres": ["Mystery", "Romance", "Thriller"], - "rating": 8.3, - "directors": ["Alfred Hitchcock"], - "actors": ["James Stewart", "Kim Novak", "Barbara Bel Geddes"], - "plot": "A former San Francisco police detective juggles wrestling with his personal demons and becoming obsessed with the hauntingly beautiful woman he has been hired to follow." - }, - { - "id": "tt0169547", - "title": "American Beauty", - "year": 1999, - "genres": ["Drama"], - "rating": 8.3, - "directors": ["Sam Mendes"], - "actors": ["Kevin Spacey", "Annette Bening", "Thora Birch"], - "plot": "A sexually frustrated suburban father has a mid-life crisis after becoming infatuated with his daughter's best friend." - }, - { - "id": "tt0091251", - "title": "Come and See", - "year": 1985, - "genres": ["Drama", "Thriller", "War"], - "rating": 8.4, - "directors": ["Elem Klimov"], - "actors": ["Aleksei Kravchenko", "Olga Mironova", "Liubomiras Laucevicius"], - "plot": "After finding an old rifle, a young boy joins the Soviet resistance movement against ruthless German forces and experiences the horrors of World War II." - }, - { - "id": "tt1853728", - "title": "Django Unchained", - "year": 2012, - "genres": ["Drama", "Western"], - "rating": 8.4, - "directors": ["Quentin Tarantino"], - "actors": ["Jamie Foxx", "Christoph Waltz", "Leonardo DiCaprio"], - "plot": "With the help of a German bounty hunter, a freed slave sets out to rescue his wife from a brutal Mississippi plantation owner." - }, - { - "id": "tt0053125", - "title": "North by Northwest", - "year": 1959, - "genres": ["Action", "Adventure", "Mystery"], - "rating": 8.3, - "directors": ["Alfred Hitchcock"], - "actors": ["Cary Grant", "Eva Marie Saint", "James Mason"], - "plot": "A New York City advertising executive goes on the run after being mistaken for a government agent by a group of foreign spies." - }, - { - "id": "tt0033467", - "title": "Citizen Kane", - "year": 1941, - "genres": ["Drama", "Mystery"], - "rating": 8.3, - "directors": ["Orson Welles"], - "actors": ["Orson Welles", "Joseph Cotten", "Dorothy Comingore"], - "plot": "Following the death of publishing tycoon Charles Foster Kane, reporters scramble to uncover the meaning of his final utterance: 'Rosebud.'" - }, - { - "id": "tt0095327", - "title": "Grave of the Fireflies", - "year": 1988, - "genres": ["Animation", "Drama", "War"], - "rating": 8.5, - "directors": ["Isao Takahata"], - "actors": ["Tsutomu Tatsumi", "Ayano Shiraishi", "Akemi Yamaguchi"], - "plot": "A young boy and his little sister struggle to survive in Japan during World War II." - }, - { - "id": "tt0045152", - "title": "Singin' in the Rain", - "year": 1952, - "genres": ["Comedy", "Musical", "Romance"], - "rating": 8.3, - "directors": ["Stanley Donen", "Gene Kelly"], - "actors": ["Gene Kelly", "Donald O'Connor", "Debbie Reynolds"], - "plot": "A silent film star falls for a chorus girl just as he and his studio are about to make the difficult transition to sound films." - }, - { - "id": "tt0053604", - "title": "The Apartment", - "year": 1960, - "genres": ["Comedy", "Drama", "Romance"], - "rating": 8.3, - "directors": ["Billy Wilder"], - "actors": ["Jack Lemmon", "Shirley MacLaine", "Fred MacMurray"], - "plot": "A man tries to rise in his company by letting its executives use his apartment for trysts, but complications and a potential love interest arise." - }, - { - "id": "tt0364569", - "title": "Oldboy", - "year": 2003, - "genres": ["Action", "Drama", "Mystery"], - "rating": 8.4, - "directors": ["Park Chan-wook"], - "actors": ["Choi Min-sik", "Yoo Ji-tae", "Kang Hye-jung"], - "plot": "After being kidnapped and imprisoned for fifteen years, Oh Dae-Su is released, only to find that he must find his captor in five days." - }, - { - "id": "tt0062622", - "title": "2001: A Space Odyssey", - "year": 1968, - "genres": ["Adventure", "Sci-Fi"], - "rating": 8.3, - "directors": ["Stanley Kubrick"], - "actors": ["Keir Dullea", "Gary Lockwood", "William Sylvester"], - "plot": "After discovering a mysterious artifact buried beneath the Lunar surface, mankind sets off on a quest to find its origins with help from intelligent supercomputer H.A.L. 9000." - }, - { - "id": "tt0211915", - "title": "Amelie", - "year": 2001, - "genres": ["Comedy", "Romance"], - "rating": 8.3, - "directors": ["Jean-Pierre Jeunet"], - "actors": ["Audrey Tautou", "Mathieu Kassovitz", "Rufus"], - "plot": "Amelie is an innocent and naive girl in Paris with her own sense of justice. She decides to help those around her and, along the way, discovers love." - }, - { - "id": "tt0040897", - "title": "The Treasure of the Sierra Madre", - "year": 1948, - "genres": ["Adventure", "Drama", "Western"], - "rating": 8.2, - "directors": ["John Huston"], - "actors": ["Humphrey Bogart", "Walter Huston", "Tim Holt"], - "plot": "Two American men searching for work in Mexico convince an old prospector to help them mine for gold in the Sierra Madre Mountains." - }, - { - "id": "tt0036775", - "title": "Double Indemnity", - "year": 1944, - "genres": ["Crime", "Drama", "Film-Noir"], - "rating": 8.3, - "directors": ["Billy Wilder"], - "actors": ["Fred MacMurray", "Barbara Stanwyck", "Edward G. Robinson"], - "plot": "A Los Angeles insurance representative lets an alluring housewife seduce him into a scheme of murder and insurance fraud." - }, - { - "id": "tt2582802", - "title": "Whiplash", - "year": 2014, - "genres": ["Drama", "Music"], - "rating": 8.5, - "directors": ["Damien Chazelle"], - "actors": ["Miles Teller", "J.K. Simmons", "Melissa Benoist"], - "plot": "A promising young drummer enrolls at a cut-throat music conservatory where his dreams of greatness are mentored by an instructor who will stop at nothing to realize a student's potential." - }, - { - "id": "tt0075314", - "title": "Taxi Driver", - "year": 1976, - "genres": ["Crime", "Drama"], - "rating": 8.2, - "directors": ["Martin Scorsese"], - "actors": ["Robert De Niro", "Jodie Foster", "Cybill Shepherd"], - "plot": "A mentally unstable veteran works as a nighttime taxi driver in New York City, where the perceived decadence and sleaze fuels his unrelenting sense of disenfranchisement." - }, - { - "id": "tt0093058", - "title": "Full Metal Jacket", - "year": 1987, - "genres": ["Drama", "War"], - "rating": 8.3, - "directors": ["Stanley Kubrick"], - "actors": ["Matthew Modine", "R. Lee Ermey", "Vincent D'Onofrio"], - "plot": "A pragmatic U.S. Marine observes the dehumanizing effects the Vietnam War has on his fellow recruits from their brutal boot camp training to the bloody street fighting in Hue." - }, - { - "id": "tt0317248", - "title": "City of God", - "year": 2002, - "genres": ["Crime", "Drama"], - "rating": 8.6, - "directors": ["Fernando Meirelles", "Katia Lund"], - "actors": ["Alexandre Rodrigues", "Leandro Firmino", "Matheus Nachtergaele"], - "plot": "In the slums of Rio, two kids' paths diverge as one struggles to become a photographer and the other a drug dealer." - }, - { - "id": "tt0070735", - "title": "The Sting", - "year": 1973, - "genres": ["Comedy", "Crime", "Drama"], - "rating": 8.3, - "directors": ["George Roy Hill"], - "actors": ["Paul Newman", "Robert Redford", "Robert Shaw"], - "plot": "Two grifters team up to pull off the ultimate con." - }, - { - "id": "tt0059578", - "title": "For a Few Dollars More", - "year": 1965, - "genres": ["Western"], - "rating": 8.2, - "directors": ["Sergio Leone"], - "actors": ["Clint Eastwood", "Lee Van Cleef", "Gian Maria Volonte"], - "plot": "Two bounty hunters with the same intentions team up to track down an escaped Mexican outlaw." - }, - { - "id": "tt0180093", - "title": "Requiem for a Dream", - "year": 2000, - "genres": ["Drama"], - "rating": 8.3, - "directors": ["Darren Aronofsky"], - "actors": ["Ellen Burstyn", "Jared Leto", "Jennifer Connelly"], - "plot": "The drug-induced utopias of four Coney Island people are shattered when their addictions run deep." - }, - { - "id": "tt0112573", - "title": "Braveheart", - "year": 1995, - "genres": ["Biography", "Drama", "History"], - "rating": 8.3, - "directors": ["Mel Gibson"], - "actors": ["Mel Gibson", "Sophie Marceau", "Patrick McGoohan"], - "plot": "Scottish warrior William Wallace leads his countrymen in a rebellion to free his homeland from the tyranny of King Edward I of England." - }, - { - "id": "tt0047396", - "title": "Rear Window", - "year": 1954, - "genres": ["Mystery", "Thriller"], - "rating": 8.5, - "directors": ["Alfred Hitchcock"], - "actors": ["James Stewart", "Grace Kelly", "Wendell Corey"], - "plot": "A wheelchair-bound photographer spies on his neighbors from his Greenwich Village courtyard apartment and becomes convinced one of them has committed murder." - }, - { - "id": "tt0892769", - "title": "How to Train Your Dragon", - "year": 2010, - "genres": ["Animation", "Action", "Adventure"], - "rating": 8.1, - "directors": ["Dean DeBlois", "Chris Sanders"], - "actors": ["Jay Baruchel", "Gerard Butler", "Christopher Mintz-Plasse"], - "plot": "A hapless young Viking who aspires to hunt dragons becomes the unlikely friend of a young dragon himself." - }, - { - "id": "tt0986264", - "title": "Taare Zameen Par", - "year": 2007, - "genres": ["Drama", "Family"], - "rating": 8.4, - "directors": ["Aamir Khan", "Amole Gupte"], - "actors": ["Darsheel Safary", "Aamir Khan", "Tanay Chheda"], - "plot": "An eight-year-old boy is thought to be a lazy trouble-maker, until the new art teacher has the patience and determination to discover the real problem behind his struggles." - }, - { - "id": "tt0361748", - "title": "Inglourious Basterds", - "year": 2009, - "genres": ["Adventure", "Drama", "War"], - "rating": 8.3, - "directors": ["Quentin Tarantino"], - "actors": ["Brad Pitt", "Diane Kruger", "Eli Roth"], - "plot": "In Nazi-occupied France during World War II, a plan to assassinate Nazi leaders by a group of Jewish U.S. soldiers coincides with a theatre owner's vengeful plans." - }, - { - "id": "tt0338013", - "title": "Eternal Sunshine of the Spotless Mind", - "year": 2004, - "genres": ["Drama", "Romance", "Sci-Fi"], - "rating": 8.3, - "directors": ["Michel Gondry"], - "actors": ["Jim Carrey", "Kate Winslet", "Tom Wilkinson"], - "plot": "When their relationship turns sour, a couple undergoes a medical procedure to have each other erased from their memories." - }, - { - "id": "tt0057115", - "title": "The Great Escape", - "year": 1963, - "genres": ["Adventure", "Drama", "History"], - "rating": 8.2, - "directors": ["John Sturges"], - "actors": ["Steve McQueen", "James Garner", "Richard Attenborough"], - "plot": "Allied prisoners of war plan for several hundred of their number to escape from a German camp during World War II." - }, - { - "id": "tt0017136", - "title": "Metropolis", - "year": 1927, - "genres": ["Drama", "Sci-Fi"], - "rating": 8.3, - "directors": ["Fritz Lang"], - "actors": ["Brigitte Helm", "Alfred Abel", "Gustav Frohlich"], - "plot": "In a futuristic city sharply divided between the working class and the city planners, the son of the city's mastermind falls in love with a working-class prophet." - }, - { - "id": "tt0043014", - "title": "Sunset Blvd.", - "year": 1950, - "genres": ["Drama", "Film-Noir"], - "rating": 8.4, - "directors": ["Billy Wilder"], - "actors": ["William Holden", "Gloria Swanson", "Erich von Stroheim"], - "plot": "A screenwriter develops a dangerous relationship with a faded film star determined to make a triumphant return." - }, - { - "id": "tt0476735", - "title": "My Father and My Son", - "year": 2005, - "genres": ["Drama", "Family"], - "rating": 8.2, - "directors": ["Cagan Irmak"], - "actors": ["Fikret Kuskan", "Cetin Tekindor", "Humeyra"], - "plot": "The political turmoil in 1980s Turkey creates a rift between a father and his son." - }, - { - "id": "tt0097576", - "title": "Indiana Jones and the Last Crusade", - "year": 1989, - "genres": ["Action", "Adventure"], - "rating": 8.2, - "directors": ["Steven Spielberg"], - "actors": ["Harrison Ford", "Sean Connery", "Alison Doody"], - "plot": "In 1938, after his father Professor Henry Jones, Sr. goes missing while pursuing the Holy Grail, Indiana Jones finds himself up against Adolf Hitler's Nazis again." - } -] diff --git a/paradedb/sample-movie-search/lambda/index.ts b/paradedb/sample-movie-search/lambda/index.ts index e3d3790b..81cf8c12 100644 --- a/paradedb/sample-movie-search/lambda/index.ts +++ b/paradedb/sample-movie-search/lambda/index.ts @@ -2,6 +2,21 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { Pool } from "pg"; import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; +interface Movie { + id: string; + title: string; + year?: number; + genres?: string[]; + rating?: number; + directors?: string[]; + actors?: string[]; + plot?: string; + release_date?: string; + rank?: number; + running_time_secs?: number; + image_url?: string; +} + const pool = new Pool({ host: process.env.PARADEDB_HOST || "paradedb.localhost.localstack.cloud", port: parseInt(process.env.PARADEDB_PORT || "5432"), @@ -70,6 +85,9 @@ export async function searchHandler( rating, directors, actors, + image_url, + running_time_secs, + plot, pdb.snippet(plot, start_tag => '', end_tag => '') as highlight, pdb.score(id) as score FROM movies @@ -94,10 +112,13 @@ export async function searchHandler( title: row.title, year: row.year, genres: row.genres, - rating: parseFloat(row.rating), + rating: row.rating ? parseFloat(row.rating) : null, directors: row.directors, actors: row.actors, + image_url: row.image_url, + running_time_secs: row.running_time_secs, highlight: row.highlight, + plot: row.plot, })), total, limit, @@ -122,7 +143,8 @@ export async function movieDetailHandler( console.log(`Fetching movie: ${movieId}`); const query = ` - SELECT id, title, year, genres, rating, directors, actors, plot + SELECT id, title, year, genres, rating, directors, actors, plot, + image_url, release_date, rank, running_time_secs FROM movies WHERE id = $1 `; @@ -139,10 +161,14 @@ export async function movieDetailHandler( title: movie.title, year: movie.year, genres: movie.genres, - rating: parseFloat(movie.rating), + rating: movie.rating ? parseFloat(movie.rating) : null, directors: movie.directors, actors: movie.actors, plot: movie.plot, + image_url: movie.image_url, + release_date: movie.release_date, + rank: movie.rank, + running_time_secs: movie.running_time_secs, }); } catch (error) { console.error("Movie detail error:", error); @@ -165,7 +191,11 @@ export async function initHandler(): Promise { rating NUMERIC(3,1), directors TEXT[], actors TEXT[], - plot TEXT + plot TEXT, + image_url TEXT, + release_date TIMESTAMPTZ, + rank INTEGER, + running_time_secs INTEGER ) `); @@ -210,7 +240,7 @@ export async function seedHandler(): Promise { const command = new GetObjectCommand({ Bucket: DATA_BUCKET, - Key: "movies.json", + Key: "movies.bulk", }); const response = await s3Client.send(command); @@ -220,41 +250,71 @@ export async function seedHandler(): Promise { return errorResponse(500, "Failed to read movie data from S3"); } - const movies = JSON.parse(bodyString); - console.log(`Loaded ${movies.length} movies from S3`); + const lines = bodyString.trim().split("\n"); + const movies: Movie[] = []; + + for (const line of lines) { + if (line.trim()) { + try { + const movie = JSON.parse(line) as Movie; + movies.push(movie); + } catch (e) { + console.warn(`Skipping invalid JSON line: ${line.substring(0, 50)}...`); + } + } + } + + console.log(`Parsed ${movies.length} movies from bulk file`); await client.query("DELETE FROM movies"); let inserted = 0; - for (const movie of movies) { - await client.query( - ` - INSERT INTO movies (id, title, year, genres, rating, directors, actors, plot) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - ON CONFLICT (id) DO UPDATE SET - title = EXCLUDED.title, - year = EXCLUDED.year, - genres = EXCLUDED.genres, - rating = EXCLUDED.rating, - directors = EXCLUDED.directors, - actors = EXCLUDED.actors, - plot = EXCLUDED.plot - `, - [ - movie.id, - movie.title, - movie.year, - movie.genres, - movie.rating, - movie.directors, - movie.actors, - movie.plot, - ] - ); - inserted++; + const batchSize = 100; + + for (let i = 0; i < movies.length; i += batchSize) { + const batch = movies.slice(i, i + batchSize); + + for (const movie of batch) { + await client.query( + ` + INSERT INTO movies (id, title, year, genres, rating, directors, actors, plot, + image_url, release_date, rank, running_time_secs) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + year = EXCLUDED.year, + genres = EXCLUDED.genres, + rating = EXCLUDED.rating, + directors = EXCLUDED.directors, + actors = EXCLUDED.actors, + plot = EXCLUDED.plot, + image_url = EXCLUDED.image_url, + release_date = EXCLUDED.release_date, + rank = EXCLUDED.rank, + running_time_secs = EXCLUDED.running_time_secs + `, + [ + movie.id, + movie.title, + movie.year || null, + movie.genres || [], + movie.rating || null, + movie.directors || [], + movie.actors || [], + movie.plot || null, + movie.image_url || null, + movie.release_date || null, + movie.rank || null, + movie.running_time_secs || null, + ] + ); + inserted++; + } + + console.log(`Inserted ${inserted}/${movies.length} movies...`); } - console.log(`Inserted ${inserted} movies`); + console.log(`Seeding complete: ${inserted} movies`); return successResponse({ message: "Data seeded successfully", diff --git a/paradedb/sample-movie-search/lib/movie-search-stack.ts b/paradedb/sample-movie-search/lib/movie-search-stack.ts index 9d42e212..9d1b7e13 100644 --- a/paradedb/sample-movie-search/lib/movie-search-stack.ts +++ b/paradedb/sample-movie-search/lib/movie-search-stack.ts @@ -107,8 +107,8 @@ export class MovieSearchStack extends cdk.Stack { handler: "index.seedHandler", code: getLambdaCode(), environment: paradeDbEnv, - timeout: cdk.Duration.seconds(120), - memorySize: 512, + timeout: cdk.Duration.minutes(10), + memorySize: 1024, }); dataBucket.grantRead(seedHandler); diff --git a/paradedb/sample-movie-search/web/style.css b/paradedb/sample-movie-search/web/style.css index dca7b899..afdd23c6 100644 --- a/paradedb/sample-movie-search/web/style.css +++ b/paradedb/sample-movie-search/web/style.css @@ -123,12 +123,42 @@ header h1 { padding: 1.5rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: box-shadow 0.2s; + display: flex; + gap: 1.25rem; } .movie-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } +.movie-poster { + flex-shrink: 0; + width: 100px; + height: 150px; + border-radius: 8px; + overflow: hidden; + background-color: #ecf0f1; + display: flex; + align-items: center; + justify-content: center; +} + +.movie-poster img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.movie-poster-placeholder { + color: #bdc3c7; + font-size: 2rem; +} + +.movie-content { + flex: 1; + min-width: 0; +} + .movie-header { display: flex; justify-content: space-between; @@ -195,6 +225,12 @@ header h1 { margin-right: 1rem; } +.movie-runtime { + color: #95a5a6; + font-size: 0.85rem; + margin-left: 0.5rem; +} + .pagination { display: flex; justify-content: center; @@ -266,6 +302,15 @@ footer a:hover { width: 100%; } + .movie-card { + flex-direction: column; + } + + .movie-poster { + width: 80px; + height: 120px; + } + .movie-header { flex-direction: column; gap: 0.5rem; From f5980c755ef2a40418db04d8fcdd90764ced4923 Mon Sep 17 00:00:00 2001 From: HarshCasper Date: Wed, 4 Feb 2026 20:26:20 +0530 Subject: [PATCH 3/3] improve README --- paradedb/sample-movie-search/README.md | 9 +- paradedb/sample-movie-search/idea.md | 294 ------------------------- 2 files changed, 1 insertion(+), 302 deletions(-) delete mode 100644 paradedb/sample-movie-search/idea.md diff --git a/paradedb/sample-movie-search/README.md b/paradedb/sample-movie-search/README.md index 8b3055ec..0de69658 100644 --- a/paradedb/sample-movie-search/README.md +++ b/paradedb/sample-movie-search/README.md @@ -175,14 +175,7 @@ make web-ui This starts a local web server at http://localhost:3000. The UI automatically connects to the API Gateway at `http://movie-search-api.execute-api.localhost.localstack.cloud:4566/dev`. -### Features - -- Movie poster images from Amazon -- Runtime display (e.g., "2h 22m") -- Genre tags -- Director and cast information -- Search result highlighting -- Pagination +image ## How It Works diff --git a/paradedb/sample-movie-search/idea.md b/paradedb/sample-movie-search/idea.md deleted file mode 100644 index 48a277ab..00000000 --- a/paradedb/sample-movie-search/idea.md +++ /dev/null @@ -1,294 +0,0 @@ -# ParadeDB Sample App: Movie Search - -## Overview - -A CDK application demonstrating integration with ParadeDB's full-text search capabilities running in LocalStack. This sample app showcases ParadeDB as a modern Elasticsearch replacement, using BM25 search with fuzzy matching and highlighting. - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ LocalStack │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ -│ │ │ │ │ │ │ │ -│ │ API Gateway │────▶│ Lambda │────▶│ ParadeDB │ │ -│ │ │ │ │ │ (pg_search) │ │ -│ └──────────────┘ └──────────────┘ └──────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────┐ │ -│ │ S3 │ │ -│ │ (movie data) │ │ -│ └──────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ - -┌──────────────────┐ -│ Static Web UI │ -│ (HTML/CSS/JS) │ -└──────────────────┘ -``` - -## Tech Stack - -| Component | Technology | -|-----------|------------| -| Infrastructure | AWS CDK (TypeScript) | -| Lambda Runtime | Node.js 22.x | -| Database | ParadeDB (PostgreSQL + pg_search) | -| Postgres Client | pg (node-postgres) | -| Data Storage | Amazon S3 | -| API | Amazon API Gateway (REST) | -| Frontend | Vanilla HTML/CSS/JS | - -## AWS Services Used - -- **AWS Lambda** - Handles search and data operations -- **Amazon API Gateway** - REST API endpoints -- **Amazon S3** - Stores movie dataset (JSON) -- **ParadeDB Extension** - Full-text search engine (runs in LocalStack) - -## API Endpoints - -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/search?q=` | Search movies with BM25 ranking | -| GET | `/movies/:id` | Get movie details by ID | - -### Search Query Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `q` | string | required | Search query (supports fuzzy matching) | -| `limit` | number | 10 | Max results to return | -| `offset` | number | 0 | Pagination offset | - -### Search Response Shape - -```json -{ - "success": true, - "data": { - "results": [ - { - "id": "tt0111161", - "title": "The Shawshank Redemption", - "year": 1994, - "genres": ["Drama"], - "rating": 9.3, - "directors": ["Frank Darabont"], - "actors": ["Tim Robbins", "Morgan Freeman"], - "highlight": "...two imprisoned men bond over a number of years..." - } - ], - "total": 1, - "limit": 10, - "offset": 0 - } -} -``` - -### Movie Detail Response - -```json -{ - "success": true, - "data": { - "id": "tt0111161", - "title": "The Shawshank Redemption", - "year": 1994, - "genres": ["Drama"], - "rating": 9.3, - "directors": ["Frank Darabont"], - "actors": ["Tim Robbins", "Morgan Freeman"], - "plot": "Two imprisoned men bond over a number of years..." - } -} -``` - -## Search Features Demonstrated - -| Feature | Description | -|---------|-------------| -| **BM25 Ranking** | Relevance scoring using BM25 algorithm - the industry standard for text search | -| **Fuzzy Matching** | Handles typos (e.g., "Godfater" finds "Godfather") | -| **Highlighting** | Returns matched text snippets with search terms wrapped in `` tags | - -## Database Schema - -```sql -CREATE TABLE movies ( - id VARCHAR(20) PRIMARY KEY, - title TEXT NOT NULL, - year INTEGER, - genres TEXT[], - rating NUMERIC(3,1), - directors TEXT[], - actors TEXT[], - plot TEXT -); - --- ParadeDB BM25 search index -CALL paradedb.create_bm25( - index_name => 'movies_search_idx', - table_name => 'movies', - key_field => 'id', - text_fields => paradedb.field('title') || paradedb.field('plot') -); -``` - -## Dataset - -- **Source**: AWS sample-movies dataset (transformed from OpenSearch format) -- **Size**: ~100 movies (curated subset for fast loading) -- **Format**: JSON stored in S3 -- **Fields**: id, title, year, genres, rating, directors, actors, plot - -## Project Structure - -``` -paradedb/sample-movie-search/ -├── README.md # Setup & usage instructions -├── idea.md # This document -├── Makefile # Development commands -├── package.json # CDK dependencies -├── tsconfig.json # TypeScript config -├── cdk.json # CDK config -├── bin/ -│ └── app.ts # CDK app entry point -├── lib/ -│ └── movie-search-stack.ts # CDK stack definition -├── lambda/ -│ ├── package.json # Lambda dependencies -│ ├── search.ts # Search handler -│ ├── movie-detail.ts # Movie detail handler -│ ├── init.ts # Schema/index creation -│ └── seed.ts # Data loading from S3 -├── data/ -│ └── movies.json # Transformed movie dataset -└── web/ - ├── index.html # Main HTML page - ├── style.css # Styling - └── script.js # Search functionality -``` - -## Setup Flow - -### 1. Start LocalStack with ParadeDB Extension - -```bash -localstack extensions install localstack-extension-paradedb -localstack start -``` - -### 2. Deploy Infrastructure - -```bash -cd paradedb/sample-movie-search -npm install -cdklocal bootstrap -cdklocal deploy -``` - -### 3. Initialize Database - -```bash -make init -``` - -This triggers a Lambda that: -- Creates the `movies` table -- Creates the BM25 search index using pg_search - -### 4. Seed Data - -```bash -make seed -``` - -This triggers a Lambda that: -- Reads `movies.json` from S3 -- Inserts all movies into ParadeDB - -### 5. Test the API - -```bash -# Search for movies -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/search?q=redemption" - -# Get movie details -curl "https://.execute-api.localhost.localstack.cloud:4566/dev/movies/tt0111161" -``` - -## Makefile Targets - -| Target | Description | -|--------|-------------| -| `make install` | Install all dependencies | -| `make deploy` | Deploy CDK stack to LocalStack | -| `make init` | Create database schema and BM25 index | -| `make seed` | Load movie data from S3 into ParadeDB | -| `make destroy` | Tear down the stack | - -## Web UI Features - -The minimal web UI provides: - -- **Search Box**: Text input with search button -- **Results List**: Movie cards displaying: - - Title - - Year - - Genres (as tags) - - Rating (stars) - - Highlighted plot snippet - -The UI is served as static files and connects to the API Gateway endpoint. - -## ParadeDB Connection - -The Lambda connects to ParadeDB via LocalStack's internal networking: - -``` -PARADEDB_HOST=paradedb.localhost.localstack.cloud -PARADEDB_PORT=5432 -PARADEDB_DATABASE=postgres -PARADEDB_USER=postgres -PARADEDB_PASSWORD=postgres -``` - -## Error Handling - -API returns minimal error responses for security: - -```json -{ - "success": false, - "error": "Search failed" -} -``` - -## What This Demo Shows - -1. **ParadeDB as Elasticsearch Replacement**: Full-text search with BM25 ranking directly in Postgres -2. **LocalStack Extension Integration**: Running ParadeDB alongside AWS services in LocalStack -3. **Serverless Search Architecture**: Lambda + API Gateway pattern for search APIs -4. **Data Pipeline**: S3 → Lambda → ParadeDB ingestion flow -5. **Modern TypeScript Stack**: CDK + Node.js 22 + TypeScript throughout - -## Not Included (By Design) - -- Faceted search / aggregations -- Analytics queries (pg_analytics) -- Automated tests -- Production error handling -- Authentication/authorization -- Caching layer - -## References - -- [ParadeDB Documentation](https://docs.paradedb.com/) -- [LocalStack Extensions](https://docs.localstack.cloud/aws/tooling/extensions/) -- [AWS CDK Local](https://github.com/localstack/aws-cdk-local) -- [WireMock Sample App](../wiremock/sample-app-runner/) (similar pattern) -- [TypeDB Sample App](https://github.com/typedb-osi/typedb-localstack-demo) (similar pattern)