diff --git a/Cargo.lock b/Cargo.lock index 5223e17de7..f50ebda890 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11532,6 +11532,8 @@ dependencies = [ "fs_extra", "include_dir", "merkle_hash", + "rivet-js-utils-embed", + "serde_json", "sha2 0.10.8", "tempfile", "tokio", @@ -13080,9 +13082,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "indexmap 2.7.0", "itoa 1.0.14", diff --git a/examples/javascript/ai-agent-shopper/catalog.ts b/examples/javascript/ai-agent-shopper/catalog.ts index 17e8736620..ec194dfaad 100644 --- a/examples/javascript/ai-agent-shopper/catalog.ts +++ b/examples/javascript/ai-agent-shopper/catalog.ts @@ -1,131 +1,25 @@ +import CATALOG_ITEMS from "./catalog_items"; + export interface CatalogItem { slug: string; name: string; price: number; // cents } -export const CATALOG: CatalogItem[] = [ - // Painting a Room - { slug: "paint-white", name: "White Paint", price: 2500 }, - { slug: "paint-blue", name: "Blue Paint", price: 2600 }, - { slug: "paint-roller", name: "Paint Roller", price: 800 }, - { slug: "paint-brush", name: "Paint Brush", price: 500 }, - { slug: "drop-cloth", name: "Drop Cloth", price: 1200 }, - { slug: "painter-tape", name: "Painter's Tape", price: 300 }, - { slug: "paint-tray", name: "Paint Tray", price: 400 }, - { slug: "paint-stirrer", name: "Paint Stirrer", price: 100 }, - { slug: "paint-primer", name: "Paint Primer", price: 1500 }, - { slug: "paint-edger", name: "Paint Edger", price: 700 }, - - // Installing Laminate Flooring - { slug: "laminate-flooring", name: "Laminate Flooring", price: 2000 }, - { slug: "flooring-underlayment", name: "Flooring Underlayment", price: 1000 }, - { slug: "flooring-trim", name: "Flooring Trim", price: 1500 }, - { slug: "flooring-spacer", name: "Flooring Spacer", price: 200 }, - { slug: "flooring-cutter", name: "Flooring Cutter", price: 3000 }, - { slug: "flooring-adhesive", name: "Flooring Adhesive", price: 1200 }, - { - slug: "flooring-tapping-block", - name: "Flooring Tapping Block", - price: 500, - }, - { slug: "flooring-pull-bar", name: "Flooring Pull Bar", price: 600 }, - { slug: "flooring-mallet", name: "Flooring Mallet", price: 800 }, - { slug: "flooring-level", name: "Flooring Level", price: 900 }, - - // Building a Deck or Patio - { slug: "deck-wood", name: "Deck Wood", price: 3500 }, - { slug: "deck-screws", name: "Deck Screws", price: 1500 }, - { slug: "deck-stain", name: "Deck Stain", price: 2500 }, - { slug: "deck-sealer", name: "Deck Sealer", price: 2000 }, - { slug: "deck-joist-hanger", name: "Deck Joist Hanger", price: 700 }, - { slug: "deck-post-cap", name: "Deck Post Cap", price: 500 }, - { slug: "deck-railing", name: "Deck Railing", price: 4000 }, - { slug: "deck-flashing", name: "Deck Flashing", price: 1000 }, - { slug: "deck-brush", name: "Deck Brush", price: 600 }, - { slug: "deck-sander", name: "Deck Sander", price: 3000 }, - - // Updating Light Fixtures - { slug: "light-fixture", name: "Light Fixture", price: 5000 }, - { slug: "light-bulb", name: "Light Bulb", price: 300 }, - { slug: "light-switch", name: "Light Switch", price: 400 }, - { slug: "light-dimmer", name: "Light Dimmer", price: 1500 }, - { slug: "light-ceiling-fan", name: "Ceiling Fan", price: 8000 }, - { slug: "light-chandelier", name: "Chandelier", price: 12000 }, - { slug: "light-wall-sconce", name: "Wall Sconce", price: 3500 }, - { slug: "light-track-lighting", name: "Track Lighting", price: 7000 }, - { slug: "light-recessed-light", name: "Recessed Light", price: 4000 }, - { slug: "light-outdoor-fixture", name: "Outdoor Fixture", price: 6000 }, - - // Bathroom Remodels - { slug: "bathroom-faucet", name: "Bathroom Faucet", price: 4500 }, - { slug: "bathroom-vanity", name: "Bathroom Vanity", price: 15000 }, - { slug: "bathroom-tile", name: "Bathroom Tile", price: 2000 }, - { slug: "bathroom-showerhead", name: "Showerhead", price: 3000 }, - { slug: "bathroom-tub", name: "Bathtub", price: 20000 }, - { slug: "bathroom-toilet", name: "Toilet", price: 10000 }, - { slug: "bathroom-mirror", name: "Bathroom Mirror", price: 5000 }, - { slug: "bathroom-plumbing-tool", name: "Plumbing Tool", price: 2500 }, - { slug: "bathroom-towel-bar", name: "Towel Bar", price: 1500 }, - { slug: "bathroom-cabinet", name: "Bathroom Cabinet", price: 8000 }, +export function searchCatalogByKeywords(keywords: string[]): CatalogItem[] { + if (!keywords.length) return []; - // Installing Shelving or Storage - { slug: "shelf-bracket", name: "Shelf Bracket", price: 800 }, - { slug: "shelf-board", name: "Shelf Board", price: 1200 }, - { slug: "shelf-wall-anchor", name: "Wall Anchor", price: 300 }, - { slug: "shelf-peg", name: "Shelf Peg", price: 200 }, - { slug: "shelf-organizer", name: "Shelf Organizer", price: 2500 }, - { slug: "shelf-closet-system", name: "Closet System", price: 10000 }, - { slug: "shelf-storage-bin", name: "Storage Bin", price: 1500 }, - { slug: "shelf-wire-shelving", name: "Wire Shelving", price: 3000 }, - { slug: "shelf-cube-storage", name: "Cube Storage", price: 4000 }, - { slug: "shelf-cabinet", name: "Storage Cabinet", price: 7000 }, + const normalizedTags = keywords.map((tag) => tag.toLowerCase()); - // Gardening and Landscaping - { slug: "garden-soil", name: "Garden Soil", price: 500 }, - { slug: "garden-mulch", name: "Mulch", price: 700 }, - { slug: "garden-plant", name: "Plant", price: 1500 }, - { slug: "garden-shovel", name: "Shovel", price: 2000 }, - { slug: "garden-rake", name: "Rake", price: 1500 }, - { slug: "garden-hose", name: "Garden Hose", price: 2500 }, - { slug: "garden-gloves", name: "Garden Gloves", price: 800 }, - { slug: "garden-wheelbarrow", name: "Wheelbarrow", price: 6000 }, - { slug: "garden-fertilizer", name: "Fertilizer", price: 1200 }, - { slug: "garden-pruner", name: "Pruner", price: 1800 }, - - // Repairing Drywall - { slug: "drywall-sheet", name: "Drywall Sheet", price: 1000 }, - { slug: "drywall-joint-compound", name: "Joint Compound", price: 800 }, - { slug: "drywall-tape", name: "Drywall Tape", price: 300 }, - { slug: "drywall-sander", name: "Drywall Sander", price: 2500 }, - { slug: "drywall-screw", name: "Drywall Screw", price: 500 }, - { slug: "drywall-corner-bead", name: "Corner Bead", price: 700 }, - { slug: "drywall-knife", name: "Drywall Knife", price: 600 }, - { slug: "drywall-saw", name: "Drywall Saw", price: 1500 }, - { slug: "drywall-anchor", name: "Drywall Anchor", price: 400 }, - { slug: "drywall-patch", name: "Drywall Patch", price: 900 }, - - // Kitchen Upgrades - { slug: "kitchen-backsplash-tile", name: "Backsplash Tile", price: 2000 }, - { slug: "kitchen-countertop", name: "Countertop", price: 15000 }, - { slug: "kitchen-cabinet-hardware", name: "Cabinet Hardware", price: 1200 }, - { slug: "kitchen-sink", name: "Kitchen Sink", price: 8000 }, - { slug: "kitchen-faucet", name: "Kitchen Faucet", price: 5000 }, - { slug: "kitchen-appliance", name: "Kitchen Appliance", price: 20000 }, - { slug: "kitchen-island", name: "Kitchen Island", price: 25000 }, - { slug: "kitchen-lighting", name: "Kitchen Lighting", price: 7000 }, - { slug: "kitchen-cabinet", name: "Kitchen Cabinet", price: 20000 }, - { slug: "kitchen-range-hood", name: "Range Hood", price: 10000 }, + return CATALOG_ITEMS.filter((item) => { + return normalizedTags.some( + (tag) => + item.slug.toLowerCase().includes(tag) || + item.name.toLowerCase().includes(tag), + ); + }); +} - // Building Furniture - { slug: "furniture-wood", name: "Furniture Wood", price: 3000 }, - { slug: "furniture-screw", name: "Furniture Screw", price: 500 }, - { slug: "furniture-drill", name: "Power Drill", price: 8000 }, - { slug: "furniture-saw", name: "Saw", price: 6000 }, - { slug: "furniture-sander", name: "Sander", price: 4000 }, - { slug: "furniture-glue", name: "Wood Glue", price: 700 }, - { slug: "furniture-clamp", name: "Clamp", price: 1500 }, - { slug: "furniture-varnish", name: "Varnish", price: 2000 }, - { slug: "furniture-hinge", name: "Hinge", price: 300 }, - { slug: "furniture-knob", name: "Knob", price: 400 }, -]; +export function getCatalogItemBySlug(slug: string): CatalogItem | undefined { + return CATALOG_ITEMS.find((item) => item.slug === slug); +} diff --git a/examples/javascript/ai-agent-shopper/catalog_items.ts b/examples/javascript/ai-agent-shopper/catalog_items.ts new file mode 100644 index 0000000000..61bfff6c15 --- /dev/null +++ b/examples/javascript/ai-agent-shopper/catalog_items.ts @@ -0,0 +1,774 @@ +import type { CatalogItem } from "./catalog"; + +const CATALOG_ITEMS: CatalogItem[] = [ + // Painting a Room + { slug: "paint-white", name: "White Paint", price: 2500 }, + { slug: "paint-blue", name: "Blue Paint", price: 2600 }, + { slug: "paint-roller", name: "Paint Roller", price: 800 }, + { slug: "paint-brush", name: "Paint Brush", price: 500 }, + { slug: "drop-cloth", name: "Drop Cloth", price: 1200 }, + { slug: "painter-tape", name: "Painter's Tape", price: 300 }, + { slug: "paint-tray", name: "Paint Tray", price: 400 }, + { slug: "paint-stirrer", name: "Paint Stirrer", price: 100 }, + { slug: "paint-primer", name: "Paint Primer", price: 1500 }, + { slug: "paint-edger", name: "Paint Edger", price: 700 }, + + // Installing Laminate Flooring + { slug: "laminate-flooring", name: "Laminate Flooring", price: 2000 }, + { + slug: "flooring-underlayment", + name: "Flooring Underlayment", + price: 1000, + }, + { slug: "flooring-trim", name: "Flooring Trim", price: 1500 }, + { slug: "flooring-spacer", name: "Flooring Spacer", price: 200 }, + { slug: "flooring-cutter", name: "Flooring Cutter", price: 3000 }, + { slug: "flooring-adhesive", name: "Flooring Adhesive", price: 1200 }, + { + slug: "flooring-tapping-block", + name: "Flooring Tapping Block", + price: 500, + }, + { slug: "flooring-pull-bar", name: "Flooring Pull Bar", price: 600 }, + { slug: "flooring-mallet", name: "Flooring Mallet", price: 800 }, + { slug: "flooring-level", name: "Flooring Level", price: 900 }, + + // Building a Deck or Patio + { slug: "deck-wood", name: "Deck Wood", price: 3500 }, + { slug: "deck-screws", name: "Deck Screws", price: 1500 }, + { slug: "deck-stain", name: "Deck Stain", price: 2500 }, + { slug: "deck-sealer", name: "Deck Sealer", price: 2000 }, + { slug: "deck-joist-hanger", name: "Deck Joist Hanger", price: 700 }, + { slug: "deck-post-cap", name: "Deck Post Cap", price: 500 }, + { slug: "deck-railing", name: "Deck Railing", price: 4000 }, + { slug: "deck-flashing", name: "Deck Flashing", price: 1000 }, + { slug: "deck-brush", name: "Deck Brush", price: 600 }, + { slug: "deck-sander", name: "Deck Sander", price: 3000 }, + + // Updating Light Fixtures + { slug: "light-fixture", name: "Light Fixture", price: 5000 }, + { slug: "light-bulb", name: "Light Bulb", price: 300 }, + { slug: "light-switch", name: "Light Switch", price: 400 }, + { slug: "light-dimmer", name: "Light Dimmer", price: 1500 }, + { slug: "light-ceiling-fan", name: "Ceiling Fan", price: 8000 }, + { slug: "light-chandelier", name: "Chandelier", price: 12000 }, + { slug: "light-wall-sconce", name: "Wall Sconce", price: 3500 }, + { slug: "light-track-lighting", name: "Track Lighting", price: 7000 }, + { slug: "light-recessed-light", name: "Recessed Light", price: 4000 }, + { slug: "light-outdoor-fixture", name: "Outdoor Fixture", price: 6000 }, + + // Bathroom Remodels + { slug: "bathroom-faucet", name: "Bathroom Faucet", price: 4500 }, + { slug: "bathroom-vanity", name: "Bathroom Vanity", price: 15000 }, + { slug: "bathroom-tile", name: "Bathroom Tile", price: 2000 }, + { slug: "bathroom-showerhead", name: "Showerhead", price: 3000 }, + { slug: "bathroom-tub", name: "Bathtub", price: 20000 }, + { slug: "bathroom-toilet", name: "Toilet", price: 10000 }, + { slug: "bathroom-mirror", name: "Bathroom Mirror", price: 5000 }, + { slug: "bathroom-plumbing-tool", name: "Plumbing Tool", price: 2500 }, + { slug: "bathroom-towel-bar", name: "Towel Bar", price: 1500 }, + { slug: "bathroom-cabinet", name: "Bathroom Cabinet", price: 8000 }, + + // Installing Shelving or Storage + { slug: "shelf-bracket", name: "Shelf Bracket", price: 800 }, + { slug: "shelf-board", name: "Shelf Board", price: 1200 }, + { slug: "shelf-wall-anchor", name: "Wall Anchor", price: 300 }, + { slug: "shelf-peg", name: "Shelf Peg", price: 200 }, + { slug: "shelf-organizer", name: "Shelf Organizer", price: 2500 }, + { slug: "shelf-closet-system", name: "Closet System", price: 10000 }, + { slug: "shelf-storage-bin", name: "Storage Bin", price: 1500 }, + { slug: "shelf-wire-shelving", name: "Wire Shelving", price: 3000 }, + { slug: "shelf-cube-storage", name: "Cube Storage", price: 4000 }, + { slug: "shelf-cabinet", name: "Storage Cabinet", price: 7000 }, + + // Gardening and Landscaping + { slug: "garden-soil", name: "Garden Soil", price: 500 }, + { slug: "garden-mulch", name: "Mulch", price: 700 }, + { slug: "garden-plant", name: "Plant", price: 1500 }, + { slug: "garden-shovel", name: "Shovel", price: 2000 }, + { slug: "garden-rake", name: "Rake", price: 1500 }, + { slug: "garden-hose", name: "Garden Hose", price: 2500 }, + { slug: "garden-gloves", name: "Garden Gloves", price: 800 }, + { slug: "garden-wheelbarrow", name: "Wheelbarrow", price: 6000 }, + { slug: "garden-fertilizer", name: "Fertilizer", price: 1200 }, + { slug: "garden-pruner", name: "Pruner", price: 1800 }, + + // Repairing Drywall + { slug: "drywall-sheet", name: "Drywall Sheet", price: 1000 }, + { slug: "drywall-joint-compound", name: "Joint Compound", price: 800 }, + { slug: "drywall-tape", name: "Drywall Tape", price: 300 }, + { slug: "drywall-sander", name: "Drywall Sander", price: 2500 }, + { slug: "drywall-screw", name: "Drywall Screw", price: 500 }, + { slug: "drywall-corner-bead", name: "Corner Bead", price: 700 }, + { slug: "drywall-knife", name: "Drywall Knife", price: 600 }, + { slug: "drywall-saw", name: "Drywall Saw", price: 1500 }, + { slug: "drywall-anchor", name: "Drywall Anchor", price: 400 }, + { slug: "drywall-patch", name: "Drywall Patch", price: 900 }, + + // Kitchen Upgrades + { slug: "kitchen-backsplash-tile", name: "Backsplash Tile", price: 2000 }, + { slug: "kitchen-countertop", name: "Countertop", price: 15000 }, + { slug: "kitchen-cabinet-hardware", name: "Cabinet Hardware", price: 1200 }, + { slug: "kitchen-sink", name: "Kitchen Sink", price: 8000 }, + { slug: "kitchen-faucet", name: "Kitchen Faucet", price: 5000 }, + { slug: "kitchen-appliance", name: "Kitchen Appliance", price: 20000 }, + { slug: "kitchen-island", name: "Kitchen Island", price: 25000 }, + { slug: "kitchen-lighting", name: "Kitchen Lighting", price: 7000 }, + { slug: "kitchen-cabinet", name: "Kitchen Cabinet", price: 20000 }, + { slug: "kitchen-range-hood", name: "Range Hood", price: 10000 }, + + // Building Furniture + { slug: "furniture-wood", name: "Furniture Wood", price: 3000 }, + { slug: "furniture-screw", name: "Furniture Screw", price: 500 }, + { slug: "furniture-drill", name: "Power Drill", price: 8000 }, + { slug: "furniture-saw", name: "Saw", price: 6000 }, + { slug: "furniture-sander", name: "Sander", price: 4000 }, + { slug: "furniture-glue", name: "Wood Glue", price: 700 }, + { slug: "furniture-clamp", name: "Clamp", price: 1500 }, + { slug: "furniture-varnish", name: "Varnish", price: 2000 }, + { slug: "furniture-hinge", name: "Hinge", price: 300 }, + { slug: "furniture-knob", name: "Knob", price: 400 }, + + // Paint Colors (expanding the existing paint section) + { slug: "paint-beige", name: "Beige Paint", price: 2500 }, + { slug: "paint-gray", name: "Gray Paint", price: 2500 }, + { slug: "paint-yellow", name: "Yellow Paint", price: 2600 }, + { slug: "paint-red", name: "Red Paint", price: 2600 }, + { slug: "paint-green", name: "Green Paint", price: 2600 }, + { slug: "paint-brown", name: "Brown Paint", price: 2500 }, + { slug: "paint-black", name: "Black Paint", price: 2500 }, + { slug: "paint-navy", name: "Navy Paint", price: 2600 }, + { slug: "paint-purple", name: "Purple Paint", price: 2600 }, + { slug: "paint-pink", name: "Pink Paint", price: 2600 }, + { slug: "paint-orange", name: "Orange Paint", price: 2600 }, + { slug: "paint-teal", name: "Teal Paint", price: 2600 }, + { slug: "paint-cream", name: "Cream Paint", price: 2500 }, + { slug: "paint-sage", name: "Sage Paint", price: 2600 }, + { slug: "paint-maroon", name: "Maroon Paint", price: 2600 }, + + // Paint Finishes + { slug: "paint-matte", name: "Matte Paint Finish", price: 2800 }, + { slug: "paint-eggshell", name: "Eggshell Paint Finish", price: 2800 }, + { slug: "paint-satin", name: "Satin Paint Finish", price: 3000 }, + { slug: "paint-semi-gloss", name: "Semi-Gloss Paint Finish", price: 3200 }, + { slug: "paint-gloss", name: "Gloss Paint Finish", price: 3400 }, + + // Additional Painting Supplies + { slug: "paint-sprayer", name: "Paint Sprayer", price: 15000 }, + { slug: "paint-ladder", name: "Painting Ladder", price: 8000 }, + { slug: "paint-respirator", name: "Paint Respirator", price: 3000 }, + { slug: "paint-goggles", name: "Safety Goggles", price: 1500 }, + { slug: "paint-extension-pole", name: "Extension Pole", price: 2500 }, + { slug: "paint-bucket", name: "Paint Bucket", price: 800 }, + { slug: "paint-strainer", name: "Paint Strainer", price: 400 }, + { + slug: "paint-mixer-attachment", + name: "Paint Mixer Attachment", + price: 1200, + }, + + // Power Tools + { slug: "power-circular-saw", name: "Circular Saw", price: 12000 }, + { slug: "power-jigsaw", name: "Jigsaw", price: 9000 }, + { + slug: "power-reciprocating-saw", + name: "Reciprocating Saw", + price: 11000, + }, + { slug: "power-angle-grinder", name: "Angle Grinder", price: 8000 }, + { slug: "power-rotary-tool", name: "Rotary Tool", price: 7000 }, + { slug: "power-heat-gun", name: "Heat Gun", price: 5000 }, + { slug: "power-router", name: "Router", price: 13000 }, + { slug: "power-planer", name: "Power Planer", price: 10000 }, + { slug: "power-oscillating-tool", name: "Oscillating Tool", price: 8000 }, + { slug: "power-impact-driver", name: "Impact Driver", price: 9000 }, + + // Hand Tools + { slug: "hand-hammer-claw", name: "Claw Hammer", price: 2500 }, + { slug: "hand-hammer-ball-peen", name: "Ball Peen Hammer", price: 2800 }, + { slug: "hand-wrench-adjustable", name: "Adjustable Wrench", price: 2000 }, + { slug: "hand-pliers-needlenose", name: "Needlenose Pliers", price: 1800 }, + { + slug: "hand-pliers-channel-lock", + name: "Channel Lock Pliers", + price: 2200, + }, + { slug: "hand-screwdriver-set", name: "Screwdriver Set", price: 3500 }, + { slug: "hand-chisel-set", name: "Wood Chisel Set", price: 4000 }, + { slug: "hand-level-torpedo", name: "Torpedo Level", price: 1500 }, + { slug: "hand-utility-knife", name: "Utility Knife", price: 1000 }, + { slug: "hand-putty-knife", name: "Putty Knife", price: 800 }, + + // Measuring and Layout Tools + { slug: "measure-tape-25ft", name: "25ft Tape Measure", price: 1500 }, + { slug: "measure-tape-100ft", name: "100ft Tape Measure", price: 2500 }, + { + slug: "measure-laser-distance", + name: "Laser Distance Measure", + price: 8000, + }, + { slug: "measure-stud-finder", name: "Stud Finder", price: 2500 }, + { slug: "measure-angle-finder", name: "Digital Angle Finder", price: 3500 }, + { slug: "measure-chalk-line", name: "Chalk Line", price: 1000 }, + { slug: "measure-carpenter-pencil", name: "Carpenter Pencil", price: 200 }, + { + slug: "measure-combination-square", + name: "Combination Square", + price: 2000, + }, + + // Fasteners and Hardware + { + slug: "fastener-wood-screw-assorted", + name: "Wood Screw Assortment", + price: 2000, + }, + { + slug: "fastener-drywall-screw-assorted", + name: "Drywall Screw Assortment", + price: 1800, + }, + { + slug: "fastener-deck-screw-assorted", + name: "Deck Screw Assortment", + price: 2500, + }, + { slug: "fastener-nail-assorted", name: "Nail Assortment", price: 1500 }, + { + slug: "fastener-anchor-assorted", + name: "Wall Anchor Assortment", + price: 1200, + }, + { slug: "fastener-bolt-assorted", name: "Bolt Assortment", price: 2200 }, + { slug: "fastener-nut-assorted", name: "Nut Assortment", price: 1000 }, + { slug: "fastener-washer-assorted", name: "Washer Assortment", price: 800 }, + + // Plumbing + { slug: "plumbing-pipe-pvc", name: "PVC Pipe", price: 1500 }, + { slug: "plumbing-pipe-copper", name: "Copper Pipe", price: 3000 }, + { slug: "plumbing-fitting-pvc", name: "PVC Fittings", price: 500 }, + { slug: "plumbing-fitting-copper", name: "Copper Fittings", price: 1000 }, + { slug: "plumbing-pipe-wrench", name: "Pipe Wrench", price: 3500 }, + { slug: "plumbing-snake", name: "Plumbing Snake", price: 4000 }, + { + slug: "plumbing-compression-fitting", + name: "Compression Fittings", + price: 800, + }, + { slug: "plumbing-pipe-insulation", name: "Pipe Insulation", price: 1000 }, + + // Electrical + { slug: "electrical-wire-14-2", name: "14-2 Electrical Wire", price: 6000 }, + { slug: "electrical-wire-12-2", name: "12-2 Electrical Wire", price: 7000 }, + { slug: "electrical-outlet", name: "Electrical Outlet", price: 500 }, + { slug: "electrical-outlet-cover", name: "Outlet Cover", price: 200 }, + { slug: "electrical-wire-stripper", name: "Wire Stripper", price: 2000 }, + { slug: "electrical-voltage-tester", name: "Voltage Tester", price: 1500 }, + { slug: "electrical-junction-box", name: "Junction Box", price: 800 }, + { slug: "electrical-conduit", name: "Electrical Conduit", price: 1500 }, + + // Safety Equipment + { slug: "safety-glasses", name: "Safety Glasses", price: 1500 }, + { slug: "safety-gloves-leather", name: "Leather Work Gloves", price: 2000 }, + { slug: "safety-gloves-nitrile", name: "Nitrile Gloves", price: 1500 }, + { slug: "safety-ear-protection", name: "Ear Protection", price: 2500 }, + { slug: "safety-dust-mask", name: "Dust Mask", price: 1000 }, + { slug: "safety-knee-pads", name: "Knee Pads", price: 2500 }, + { slug: "safety-first-aid", name: "First Aid Kit", price: 3000 }, + { + slug: "safety-fire-extinguisher", + name: "Fire Extinguisher", + price: 5000, + }, + + // HVAC + { slug: "hvac-air-filter", name: "Air Filter", price: 2000 }, + { slug: "hvac-duct-tape", name: "HVAC Duct Tape", price: 1500 }, + { slug: "hvac-thermostat", name: "Programmable Thermostat", price: 8000 }, + { slug: "hvac-vent-cover", name: "Vent Cover", price: 1200 }, + { slug: "hvac-insulation", name: "HVAC Insulation", price: 3000 }, + { slug: "hvac-refrigerant", name: "AC Refrigerant", price: 5000 }, + + // Windows and Doors + { slug: "door-knob-brass", name: "Brass Door Knob", price: 3000 }, + { slug: "door-knob-nickel", name: "Nickel Door Knob", price: 3000 }, + { slug: "door-knob-bronze", name: "Bronze Door Knob", price: 3000 }, + { slug: "door-hinge-brass", name: "Brass Door Hinge", price: 1000 }, + { slug: "door-hinge-nickel", name: "Nickel Door Hinge", price: 1000 }, + { slug: "door-hinge-bronze", name: "Bronze Door Hinge", price: 1000 }, + { slug: "window-lock", name: "Window Lock", price: 1500 }, + { slug: "door-sweep", name: "Door Sweep", price: 1200 }, + { slug: "door-weatherstrip", name: "Door Weatherstripping", price: 1500 }, + { + slug: "window-weatherstrip", + name: "Window Weatherstripping", + price: 1500, + }, + + // Outdoor and Garden Additional Items + { slug: "outdoor-landscape-fabric", name: "Landscape Fabric", price: 2000 }, + { slug: "outdoor-edging", name: "Garden Edging", price: 1500 }, + { slug: "outdoor-sprinkler", name: "Garden Sprinkler", price: 2500 }, + { slug: "outdoor-timer", name: "Water Timer", price: 3000 }, + { slug: "outdoor-trellis", name: "Garden Trellis", price: 4000 }, + { slug: "outdoor-spray-nozzle", name: "Spray Nozzle", price: 1500 }, + { slug: "outdoor-plant-stakes", name: "Plant Stakes", price: 800 }, + { slug: "outdoor-potting-soil", name: "Potting Soil", price: 1000 }, + + // Cabinet Hardware Colors + { slug: "cabinet-pull-brass", name: "Brass Cabinet Pull", price: 800 }, + { slug: "cabinet-pull-nickel", name: "Nickel Cabinet Pull", price: 800 }, + { slug: "cabinet-pull-bronze", name: "Bronze Cabinet Pull", price: 800 }, + { slug: "cabinet-pull-black", name: "Black Cabinet Pull", price: 800 }, + { slug: "cabinet-pull-copper", name: "Copper Cabinet Pull", price: 800 }, + { slug: "cabinet-knob-brass", name: "Brass Cabinet Knob", price: 600 }, + { slug: "cabinet-knob-nickel", name: "Nickel Cabinet Knob", price: 600 }, + { slug: "cabinet-knob-bronze", name: "Bronze Cabinet Knob", price: 600 }, + { slug: "cabinet-knob-black", name: "Black Cabinet Knob", price: 600 }, + { slug: "cabinet-knob-copper", name: "Copper Cabinet Knob", price: 600 }, + + // Lumber and Building Materials + { slug: "lumber-2x4-8ft", name: "2x4 Lumber 8ft", price: 800 }, + { slug: "lumber-2x4-10ft", name: "2x4 Lumber 10ft", price: 1000 }, + { slug: "lumber-2x4-12ft", name: "2x4 Lumber 12ft", price: 1200 }, + { slug: "lumber-2x6-8ft", name: "2x6 Lumber 8ft", price: 1200 }, + { slug: "lumber-2x6-10ft", name: "2x6 Lumber 10ft", price: 1500 }, + { slug: "lumber-2x6-12ft", name: "2x6 Lumber 12ft", price: 1800 }, + { slug: "lumber-4x4-8ft", name: "4x4 Lumber 8ft", price: 2000 }, + { slug: "lumber-4x4-10ft", name: "4x4 Lumber 10ft", price: 2500 }, + { slug: "lumber-4x4-12ft", name: "4x4 Lumber 12ft", price: 3000 }, + { slug: "plywood-1/2-4x8", name: '1/2" Plywood 4x8', price: 3500 }, + { slug: "plywood-3/4-4x8", name: '3/4" Plywood 4x8', price: 4500 }, + { slug: "osb-7/16-4x8", name: '7/16" OSB 4x8', price: 2500 }, + { slug: "mdf-3/4-4x8", name: '3/4" MDF 4x8', price: 3000 }, + + // More Paint Colors (Specialty) + { slug: "paint-metallic-gold", name: "Metallic Gold Paint", price: 3500 }, + { + slug: "paint-metallic-silver", + name: "Metallic Silver Paint", + price: 3500, + }, + { + slug: "paint-metallic-copper", + name: "Metallic Copper Paint", + price: 3500, + }, + { + slug: "paint-metallic-bronze", + name: "Metallic Bronze Paint", + price: 3500, + }, + { slug: "paint-glow-white", name: "Glow-in-Dark White Paint", price: 4000 }, + { slug: "paint-glow-green", name: "Glow-in-Dark Green Paint", price: 4000 }, + { slug: "paint-glow-blue", name: "Glow-in-Dark Blue Paint", price: 4000 }, + { slug: "paint-chalkboard", name: "Chalkboard Paint", price: 3000 }, + { slug: "paint-magnetic", name: "Magnetic Paint", price: 3500 }, + { slug: "paint-textured-sand", name: "Textured Sand Paint", price: 3000 }, + + // Concrete and Masonry + { slug: "concrete-mix-60lb", name: "Concrete Mix 60lb", price: 800 }, + { slug: "concrete-mix-80lb", name: "Concrete Mix 80lb", price: 1000 }, + { slug: "mortar-mix-60lb", name: "Mortar Mix 60lb", price: 900 }, + { slug: "concrete-block-8in", name: '8" Concrete Block', price: 300 }, + { slug: "concrete-block-6in", name: '6" Concrete Block', price: 250 }, + { slug: "brick-red", name: "Red Brick", price: 100 }, + { slug: "brick-brown", name: "Brown Brick", price: 100 }, + { slug: "brick-gray", name: "Gray Brick", price: 100 }, + { slug: "concrete-sealer-1gal", name: "Concrete Sealer 1gal", price: 3000 }, + { slug: "concrete-color-brown", name: "Brown Concrete Color", price: 1500 }, + { slug: "concrete-color-gray", name: "Gray Concrete Color", price: 1500 }, + { + slug: "concrete-color-terra", + name: "Terra Cotta Concrete Color", + price: 1500, + }, + + // More Power Tool Accessories + { slug: "drill-bit-wood-set", name: "Wood Drill Bit Set", price: 3000 }, + { + slug: "drill-bit-masonry-set", + name: "Masonry Drill Bit Set", + price: 3500, + }, + { slug: "drill-bit-metal-set", name: "Metal Drill Bit Set", price: 4000 }, + { slug: "saw-blade-7-1/4", name: '7-1/4" Circular Saw Blade', price: 2500 }, + { slug: "saw-blade-10", name: '10" Table Saw Blade', price: 4000 }, + { slug: "saw-blade-12", name: '12" Miter Saw Blade', price: 5000 }, + { slug: "router-bit-set", name: "Router Bit Set", price: 8000 }, + { + slug: "sanding-disc-5in-60", + name: '5" 60-Grit Sanding Disc', + price: 500, + }, + { + slug: "sanding-disc-5in-120", + name: '5" 120-Grit Sanding Disc', + price: 500, + }, + { + slug: "sanding-disc-5in-220", + name: '5" 220-Grit Sanding Disc', + price: 500, + }, + + // Additional Tool Storage + { slug: "toolbox-plastic-22in", name: '22" Plastic Toolbox', price: 3000 }, + { slug: "toolbox-metal-26in", name: '26" Metal Toolbox', price: 5000 }, + { slug: "tool-chest-5-drawer", name: "5-Drawer Tool Chest", price: 15000 }, + { + slug: "tool-cabinet-rolling", + name: "Rolling Tool Cabinet", + price: 25000, + }, + { slug: "tool-bag-18in", name: '18" Tool Bag', price: 4000 }, + { slug: "tool-belt-leather", name: "Leather Tool Belt", price: 6000 }, + { slug: "tool-pouch-nail", name: "Nail Pouch", price: 2000 }, + { + slug: "tool-bucket-organizer", + name: "Bucket Tool Organizer", + price: 2500, + }, + + // Roofing Materials + { slug: "shingle-black", name: "Black Roof Shingles", price: 3000 }, + { slug: "shingle-brown", name: "Brown Roof Shingles", price: 3000 }, + { slug: "shingle-gray", name: "Gray Roof Shingles", price: 3000 }, + { slug: "shingle-green", name: "Green Roof Shingles", price: 3000 }, + { slug: "roofing-felt-15lb", name: "15lb Roofing Felt", price: 2000 }, + { slug: "roofing-felt-30lb", name: "30lb Roofing Felt", price: 2500 }, + { slug: "roof-vent", name: "Roof Vent", price: 1500 }, + { slug: "roof-flashing", name: "Roof Flashing", price: 1000 }, + { slug: "gutter-white-10ft", name: "10ft White Gutter", price: 1500 }, + { slug: "gutter-brown-10ft", name: "10ft Brown Gutter", price: 1500 }, + + // More Plumbing Supplies + { slug: "pipe-pex-1/2in-100ft", name: '1/2" PEX Pipe 100ft', price: 5000 }, + { slug: "pipe-pex-3/4in-100ft", name: '3/4" PEX Pipe 100ft', price: 7000 }, + { slug: "pipe-cpvc-1/2in-10ft", name: '1/2" CPVC Pipe 10ft', price: 1000 }, + { slug: "pipe-cpvc-3/4in-10ft", name: '3/4" CPVC Pipe 10ft', price: 1500 }, + { + slug: "pipe-black-1/2in-10ft", + name: '1/2" Black Pipe 10ft', + price: 2000, + }, + { + slug: "pipe-black-3/4in-10ft", + name: '3/4" Black Pipe 10ft', + price: 2500, + }, + { + slug: "water-filter-whole", + name: "Whole House Water Filter", + price: 15000, + }, + { slug: "water-softener", name: "Water Softener", price: 30000 }, + { slug: "water-heater-40gal", name: "40gal Water Heater", price: 45000 }, + { slug: "water-heater-50gal", name: "50gal Water Heater", price: 55000 }, + + // More Electrical Supplies + { slug: "wire-14-3", name: "14-3 Electrical Wire", price: 7000 }, + { slug: "wire-12-3", name: "12-3 Electrical Wire", price: 8000 }, + { slug: "wire-10-2", name: "10-2 Electrical Wire", price: 9000 }, + { slug: "wire-connector-red", name: "Red Wire Connectors", price: 500 }, + { + slug: "wire-connector-yellow", + name: "Yellow Wire Connectors", + price: 500, + }, + { slug: "wire-connector-blue", name: "Blue Wire Connectors", price: 500 }, + { + slug: "electrical-box-old-work", + name: "Old Work Electrical Box", + price: 400, + }, + { + slug: "electrical-box-new-work", + name: "New Work Electrical Box", + price: 300, + }, + { slug: "conduit-1/2in-10ft", name: '1/2" Conduit 10ft', price: 800 }, + { slug: "conduit-3/4in-10ft", name: '3/4" Conduit 10ft', price: 1000 }, + + // Outdoor Living + { slug: "paver-gray", name: "Gray Paver", price: 300 }, + { slug: "paver-red", name: "Red Paver", price: 300 }, + { slug: "paver-tan", name: "Tan Paver", price: 300 }, + { slug: "retaining-wall-block", name: "Retaining Wall Block", price: 500 }, + { + slug: "landscape-stone-white", + name: "White Landscape Stone", + price: 400, + }, + { slug: "landscape-stone-red", name: "Red Landscape Stone", price: 400 }, + { + slug: "landscape-stone-black", + name: "Black Landscape Stone", + price: 400, + }, + { slug: "landscape-timber", name: "Landscape Timber", price: 800 }, + { slug: "outdoor-fire-pit", name: "Fire Pit Kit", price: 15000 }, + { slug: "patio-umbrella", name: "Patio Umbrella", price: 10000 }, + + // Lawn Care Equipment + { slug: "mower-push", name: "Push Lawn Mower", price: 25000 }, + { + slug: "mower-self-propelled", + name: "Self-Propelled Mower", + price: 35000, + }, + { slug: "trimmer-gas", name: "Gas String Trimmer", price: 15000 }, + { slug: "trimmer-battery", name: "Battery String Trimmer", price: 20000 }, + { slug: "blower-gas", name: "Gas Leaf Blower", price: 15000 }, + { slug: "blower-battery", name: "Battery Leaf Blower", price: 20000 }, + { slug: "edger-gas", name: "Gas Lawn Edger", price: 20000 }, + { slug: "sprayer-2gal", name: "2gal Garden Sprayer", price: 3000 }, + { slug: "spreader-broadcast", name: "Broadcast Spreader", price: 5000 }, + { slug: "spreader-drop", name: "Drop Spreader", price: 4000 }, + + // Cleaning Supplies + { slug: "vacuum-wet-dry", name: "Wet/Dry Vacuum", price: 10000 }, + { slug: "pressure-washer", name: "Pressure Washer", price: 25000 }, + { slug: "broom-push", name: "Push Broom", price: 2000 }, + { slug: "mop-commercial", name: "Commercial Mop", price: 2500 }, + { slug: "bucket-5gal", name: "5gal Bucket", price: 500 }, + { slug: "cleaner-concrete", name: "Concrete Cleaner", price: 2000 }, + { slug: "cleaner-wood", name: "Wood Cleaner", price: 1500 }, + { slug: "degreaser", name: "Heavy Duty Degreaser", price: 1800 }, + + // Workshop Equipment + { slug: "workbench-6ft", name: "6ft Workbench", price: 20000 }, + { slug: "vise-6in", name: '6" Bench Vise', price: 8000 }, + { slug: "air-compressor-6gal", name: "6gal Air Compressor", price: 15000 }, + { slug: "air-hose-50ft", name: "50ft Air Hose", price: 3000 }, + { slug: "shop-fan-24in", name: '24" Shop Fan', price: 8000 }, + { slug: "shop-vac-12gal", name: "12gal Shop Vacuum", price: 10000 }, + { slug: "work-light-led", name: "LED Work Light", price: 5000 }, + { slug: "extension-cord-50ft", name: "50ft Extension Cord", price: 3000 }, + + // More Safety Equipment + { slug: "hard-hat-white", name: "White Hard Hat", price: 2000 }, + { slug: "hard-hat-yellow", name: "Yellow Hard Hat", price: 2000 }, + { slug: "safety-vest-l", name: "Large Safety Vest", price: 1500 }, + { slug: "safety-vest-xl", name: "XL Safety Vest", price: 1500 }, + { slug: "work-boots-9", name: "Size 9 Work Boots", price: 8000 }, + { slug: "work-boots-10", name: "Size 10 Work Boots", price: 8000 }, + { slug: "work-boots-11", name: "Size 11 Work Boots", price: 8000 }, + { slug: "work-boots-12", name: "Size 12 Work Boots", price: 8000 }, + { slug: "work-boots-13", name: "Size 13 Work Boots", price: 8000 }, + { + slug: "safety-glasses-tinted", + name: "Tinted Safety Glasses", + price: 2000, + }, + { slug: "safety-glasses-clear", name: "Clear Safety Glasses", price: 1500 }, + { slug: "face-shield", name: "Full Face Shield", price: 3000 }, + { slug: "respirator-half", name: "Half-Face Respirator", price: 4000 }, + { slug: "respirator-full", name: "Full-Face Respirator", price: 8000 }, + { + slug: "respirator-filter-p100", + name: "P100 Respirator Filters", + price: 2000, + }, + { + slug: "respirator-filter-organic", + name: "Organic Vapor Filters", + price: 2500, + }, + + // Insulation Materials + { + slug: "insulation-r13-kraft", + name: "R13 Kraft Faced Insulation", + price: 4000, + }, + { + slug: "insulation-r19-kraft", + name: "R19 Kraft Faced Insulation", + price: 5000, + }, + { + slug: "insulation-r30-unfaced", + name: "R30 Unfaced Insulation", + price: 6000, + }, + { + slug: "insulation-foam-board-1in", + name: '1" Foam Board Insulation', + price: 2500, + }, + { + slug: "insulation-foam-board-2in", + name: '2" Foam Board Insulation', + price: 4000, + }, + { + slug: "insulation-spray-foam", + name: "Spray Foam Insulation Kit", + price: 8000, + }, + { slug: "insulation-pipe-wrap", name: "Pipe Insulation Wrap", price: 500 }, + { slug: "insulation-tape", name: "Insulation Tape", price: 800 }, + { + slug: "weatherstrip-door-bottom", + name: "Door Bottom Weatherstrip", + price: 1200, + }, + { + slug: "weatherstrip-window-v", + name: "V-Seal Window Weatherstrip", + price: 1000, + }, + + // More Plumbing Fixtures + { slug: "toilet-white-round", name: "White Round Toilet", price: 15000 }, + { + slug: "toilet-white-elongated", + name: "White Elongated Toilet", + price: 18000, + }, + { slug: "toilet-beige-round", name: "Beige Round Toilet", price: 15000 }, + { + slug: "toilet-beige-elongated", + name: "Beige Elongated Toilet", + price: 18000, + }, + { slug: "sink-bathroom-white", name: "White Bathroom Sink", price: 8000 }, + { slug: "sink-bathroom-beige", name: "Beige Bathroom Sink", price: 8000 }, + { + slug: "sink-kitchen-stainless", + name: "Stainless Kitchen Sink", + price: 12000, + }, + { slug: "sink-kitchen-white", name: "White Kitchen Sink", price: 10000 }, + { + slug: "faucet-bathroom-chrome", + name: "Chrome Bathroom Faucet", + price: 5000, + }, + { + slug: "faucet-bathroom-bronze", + name: "Bronze Bathroom Faucet", + price: 6000, + }, + { + slug: "faucet-kitchen-chrome", + name: "Chrome Kitchen Faucet", + price: 8000, + }, + { + slug: "faucet-kitchen-bronze", + name: "Bronze Kitchen Faucet", + price: 9000, + }, + + // More Power Tools + { slug: "table-saw-10in", name: '10" Table Saw', price: 50000 }, + { slug: "miter-saw-10in", name: '10" Miter Saw', price: 30000 }, + { slug: "miter-saw-12in", name: '12" Miter Saw', price: 40000 }, + { slug: "band-saw-9in", name: '9" Band Saw', price: 35000 }, + { slug: "band-saw-14in", name: '14" Band Saw', price: 45000 }, + { slug: "drill-press-10in", name: '10" Drill Press', price: 25000 }, + { slug: "drill-press-12in", name: '12" Drill Press', price: 35000 }, + { slug: "air-nailer-brad", name: "Brad Nailer", price: 12000 }, + { slug: "air-nailer-finish", name: "Finish Nailer", price: 15000 }, + { slug: "air-nailer-framing", name: "Framing Nailer", price: 25000 }, + + // More Fasteners + { slug: "nail-brad-1in", name: '1" Brad Nails', price: 500 }, + { slug: "nail-brad-1-1/4in", name: '1-1/4" Brad Nails', price: 600 }, + { slug: "nail-brad-1-1/2in", name: '1-1/2" Brad Nails', price: 700 }, + { slug: "nail-finish-2in", name: '2" Finish Nails', price: 800 }, + { slug: "nail-finish-2-1/2in", name: '2-1/2" Finish Nails', price: 900 }, + { slug: "nail-finish-3in", name: '3" Finish Nails', price: 1000 }, + { slug: "nail-framing-3in", name: '3" Framing Nails', price: 1200 }, + { slug: "nail-framing-3-1/2in", name: '3-1/2" Framing Nails', price: 1300 }, + { + slug: "screw-deck-2-1/2in-tan", + name: '2-1/2" Tan Deck Screws', + price: 1500, + }, + { + slug: "screw-deck-2-1/2in-brown", + name: '2-1/2" Brown Deck Screws', + price: 1500, + }, + + // More Garden Supplies + { slug: "plant-rose-red", name: "Red Rose Bush", price: 2500 }, + { slug: "plant-rose-pink", name: "Pink Rose Bush", price: 2500 }, + { slug: "plant-rose-yellow", name: "Yellow Rose Bush", price: 2500 }, + { slug: "plant-hydrangea-blue", name: "Blue Hydrangea", price: 3000 }, + { slug: "plant-hydrangea-pink", name: "Pink Hydrangea", price: 3000 }, + { slug: "plant-evergreen-small", name: "Small Evergreen", price: 4000 }, + { slug: "plant-evergreen-medium", name: "Medium Evergreen", price: 6000 }, + { slug: "plant-fruit-apple", name: "Apple Tree", price: 8000 }, + { slug: "plant-fruit-peach", name: "Peach Tree", price: 8000 }, + { slug: "plant-fruit-cherry", name: "Cherry Tree", price: 8000 }, + + // More Outdoor Power Equipment + { slug: "chainsaw-16in-gas", name: '16" Gas Chainsaw', price: 25000 }, + { slug: "chainsaw-18in-gas", name: '18" Gas Chainsaw', price: 30000 }, + { + slug: "chainsaw-16in-battery", + name: '16" Battery Chainsaw', + price: 35000, + }, + { slug: "hedge-trimmer-gas", name: "Gas Hedge Trimmer", price: 20000 }, + { + slug: "hedge-trimmer-battery", + name: "Battery Hedge Trimmer", + price: 25000, + }, + { slug: "tiller-front-tine", name: "Front Tine Tiller", price: 40000 }, + { slug: "tiller-rear-tine", name: "Rear Tine Tiller", price: 60000 }, + { slug: "aerator-push", name: "Push Aerator", price: 15000 }, + { slug: "aerator-tow", name: "Tow-Behind Aerator", price: 25000 }, + { slug: "dethatcher-electric", name: "Electric Dethatcher", price: 20000 }, + + // Storage Solutions + { + slug: "shelf-garage-5tier", + name: "5-Tier Garage Shelving", + price: 12000, + }, + { slug: "cabinet-garage-wall", name: "Garage Wall Cabinet", price: 15000 }, + { slug: "cabinet-garage-base", name: "Garage Base Cabinet", price: 20000 }, + { slug: "storage-hook-bike", name: "Bike Storage Hook", price: 1500 }, + { slug: "storage-hook-ladder", name: "Ladder Storage Hook", price: 2000 }, + { slug: "storage-rack-lumber", name: "Lumber Storage Rack", price: 8000 }, + { slug: "storage-bin-tote", name: "Storage Tote Bin", price: 1500 }, + { slug: "storage-bin-wheeled", name: "Wheeled Storage Bin", price: 2500 }, + { slug: "pegboard-white", name: "White Pegboard", price: 2000 }, + { slug: "pegboard-brown", name: "Brown Pegboard", price: 2000 }, + + // Smart Home Products + { slug: "smart-thermostat", name: "Smart Thermostat", price: 25000 }, + { slug: "smart-doorbell", name: "Smart Doorbell", price: 20000 }, + { slug: "smart-lock-deadbolt", name: "Smart Deadbolt", price: 15000 }, + { slug: "smart-garage-opener", name: "Smart Garage Opener", price: 30000 }, + { slug: "smart-light-switch", name: "Smart Light Switch", price: 4000 }, + { slug: "smart-outlet", name: "Smart Outlet", price: 3000 }, + { slug: "smart-flood-sensor", name: "Smart Flood Sensor", price: 5000 }, + { slug: "smart-smoke-detector", name: "Smart Smoke Detector", price: 8000 }, + { + slug: "smart-camera-outdoor", + name: "Outdoor Smart Camera", + price: 15000, + }, + { slug: "smart-camera-indoor", name: "Indoor Smart Camera", price: 12000 }, + + // Ventilation Equipment + { slug: "fan-bathroom-50cfm", name: "50 CFM Bathroom Fan", price: 5000 }, + { slug: "fan-bathroom-80cfm", name: "80 CFM Bathroom Fan", price: 7000 }, + { slug: "fan-bathroom-110cfm", name: "110 CFM Bathroom Fan", price: 9000 }, + { slug: "fan-attic-powered", name: "Powered Attic Fan", price: 15000 }, + { slug: "vent-roof-static", name: "Static Roof Vent", price: 3000 }, + { slug: "vent-soffit-white", name: "White Soffit Vent", price: 500 }, + { slug: "vent-soffit-brown", name: "Brown Soffit Vent", price: 500 }, + { slug: "vent-gable-white", name: "White Gable Vent", price: 2500 }, + { slug: "vent-gable-brown", name: "Brown Gable Vent", price: 2500 }, + { slug: "duct-flexible-6in", name: '6" Flexible Duct', price: 2000 }, +]; + +export default CATALOG_ITEMS; diff --git a/examples/javascript/ai-agent-shopper/cli.ts b/examples/javascript/ai-agent-shopper/cli.ts new file mode 100644 index 0000000000..7c8eb47bf0 --- /dev/null +++ b/examples/javascript/ai-agent-shopper/cli.ts @@ -0,0 +1,155 @@ +import { intro, isCancel, outro, text } from "@clack/prompts"; +import { TestClient } from "@rivet-gg/actor-client/test"; +import { assertExists } from "@std/assert"; +import type { CoreMessage } from "ai"; +import colors from "picocolors"; +import type ShopperAgent from "./shopper_agent"; +import type { ShoppingCartItem } from "./shopper_agent"; + +function displayCart(cart: ShoppingCartItem[]) { + console.log(`\n${colors.yellow("Current Shopping Cart:")}`); + if (cart.length === 0) { + console.log(colors.dim("(empty)")); + } else { + for (const item of cart) { + console.log(colors.green(`- ${item.slug}: ${item.count} units`)); + } + } + console.log(); +} + +function displayMessages(messages: CoreMessage[]) { + console.log(`\n${colors.yellow("Message History:")}`); + if (messages.length === 0) { + console.log(colors.dim("(no messages)")); + } else { + for (const msg of messages) { + console.log(colors.dim(`\n[${msg.role}]:`)); + console.log(colors.white(msg.content)); + } + } + console.log(); +} + +async function main() { + // Initialize the client + const client = new TestClient(); + + // Get OpenAI key from environment + const openaiKey = process.env.OPENAI_KEY; + assertExists(openaiKey, "OPENAI_KEY environment variable is required"); + + // Connect to the shopper agent + const shopperAgent = await client.get( + { + name: "shopper_agent", + }, + { + parameters: { + openaiKey, + }, + }, + ); + + // Set up text streaming with better formatting + shopperAgent.on("textPart", (text) => { + process.stdout.write(colors.cyan(text)); + }); + + shopperAgent.on("textFinish", () => { + process.stdout.write("\n\n"); + }); + + // Show welcome message + intro(colors.blue("Welcome to the Hardware Store Assistant!")); + + // Replace the initial message history and cart display with helper functions + const messages = await shopperAgent.getMessages(); + displayMessages(messages); + + const cart = await shopperAgent.getShoppingCart(); + displayCart(cart); + + // Main interaction loop + while (true) { + const question = await text({ + message: + "How can I help you? (type '/cart' to view cart, '/messages' to view history, '/search' to search catalog, '/exit' to quit)", + placeholder: "What tools do you recommend for...", + }); + + if (isCancel(question) || question === "/exit") { + break; + } + + if (question.startsWith("/")) { + if (question === "/cart") { + const cart = await shopperAgent.getShoppingCart(); + displayCart(cart); + continue; + } + + if (question === "/messages") { + const messages = await shopperAgent.getMessages(); + displayMessages(messages); + continue; + } + + if (question.startsWith("/search")) { + const searchQuery = question.slice(7).trim(); // Remove "/search " from the start + if (!searchQuery) { + console.log( + colors.dim( + "\nPlease provide search terms after /search, e.g. '/search hammer, nails'\n", + ), + ); + continue; + } + + const searchTerms = searchQuery + .split(/[^a-zA-Z]+/) + .map((term) => term.trim().toLowerCase()) + .filter((term) => term.length > 0); + const results = await shopperAgent.searchCatalog(searchTerms); + + console.log(`\n${colors.yellow("Search Results:")}`); + if (results.length === 0) { + console.log(colors.dim("(no results)")); + } else { + for (const item of results) { + console.log( + colors.green(`- ${item.slug}: ${item.name}`), + ); + console.log(colors.dim(` ${item.description}`)); + console.log(colors.blue(` Price: $${item.price}`)); + } + } + console.log(); + continue; + } + + if (question === "/exit") { + break; + } + + // If none of the valid commands match, throw an error + throw new Error(`Invalid command: ${question}`); + } + + if (question) { + try { + await shopperAgent.processPrompt(question); + } catch (error) { + console.error(colors.red("Error:"), error.message); + } + } + } + + // Cleanup + await shopperAgent.disconnect(); + outro(colors.blue("Thanks for shopping with us!")); +} + +if (import.meta.main) { + main().catch(console.error); +} diff --git a/examples/javascript/ai-agent-shopper/deno.json b/examples/javascript/ai-agent-shopper/deno.json deleted file mode 100644 index 04b51d5143..0000000000 --- a/examples/javascript/ai-agent-shopper/deno.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "imports": { - "@ai-sdk/openai": "npm:@ai-sdk/openai@^1.0.16", - "ai": "npm:ai@^4.1.0", - "@core/asyncutil": "jsr:@core/asyncutil@^1.2.0", - "@cross/env": "jsr:@cross/env@^1.0.2", - "@rivet-gg/api": "npm:@rivet-gg/api@0.0.1-rc.5", - "@std/assert": "jsr:@std/assert@^1.0.8", - "@std/async": "jsr:@std/async@^1.0.9", - "@std/cbor": "jsr:@std/cbor@^0.1.3", - "@std/log": "jsr:@std/log@^0.224.11", - "hono": "jsr:@hono/hono@^4.6.12", - "@rivet-gg/actor-core": "../../../sdks/actor/core/src/mod.ts", - "@rivet-gg/actor": "../../../sdks/actor/runtime/src/mod.ts", - "@rivet-gg/actor-protocol/ws": "../../../sdks/actor/protocol/src/ws/mod.ts", - "@rivet-gg/actor-protocol/ws/to_server": "../../../sdks/actor/protocol/src/ws/to_server.ts", - "@rivet-gg/actor-protocol/ws/to_client": "../../../sdks/actor/protocol/src/ws/to_client.ts", - "@rivet-gg/actor-protocol/http/inspect": "../../../sdks/actor/protocol/src/http/inspect.ts", - "@rivet-gg/actor-common/utils": "../../../sdks/actor/common/src/utils.ts", - "@rivet-gg/actor-common/reflect": "../../../sdks/actor/common/src/reflect.ts", - "@rivet-gg/actor-common/log": "../../../sdks/actor/common/src/log.ts", - "@rivet-gg/actor-common/network": "../../../sdks/actor/common/src/network.ts", - "@rivet-gg/actor-client": "../../../sdks/actor/client/src/mod.ts", - "@rivet-gg/actor-client/test": "../../../sdks/actor/client/src/test.ts", - "on-change": "npm:on-change@^5.0.1", - "zod": "npm:zod@3.24.1", - "@rivet-gg/manager-protocol": "../../../sdks/actor/manager-protocol/src/mod.ts", - "@rivet-gg/manager-protocol/query": "../../../sdks/actor/manager-protocol/src/query.ts" - }, - "fmt": { - "useTabs": true - } -} diff --git a/examples/javascript/ai-agent-shopper/deno.lock b/examples/javascript/ai-agent-shopper/deno.lock deleted file mode 100644 index 1d233f7e22..0000000000 --- a/examples/javascript/ai-agent-shopper/deno.lock +++ /dev/null @@ -1,269 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@core/asyncutil@^1.2.0": "1.2.0", - "jsr:@hono/hono@^4.6.12": "4.6.16", - "jsr:@std/assert@^1.0.8": "1.0.10", - "jsr:@std/async@^1.0.9": "1.0.9", - "jsr:@std/bytes@^1.0.4": "1.0.4", - "jsr:@std/cbor@~0.1.3": "0.1.5", - "jsr:@std/fmt@^1.0.4": "1.0.4", - "jsr:@std/fs@^1.0.9": "1.0.9", - "jsr:@std/internal@^1.0.5": "1.0.5", - "jsr:@std/io@0.225": "0.225.0", - "jsr:@std/log@~0.224.11": "0.224.13", - "jsr:@std/streams@^1.0.8": "1.0.8", - "npm:@ai-sdk/openai@^1.0.16": "1.1.0_zod@3.24.1", - "npm:@types/node@*": "22.5.4", - "npm:ai@^4.1.0": "4.1.0", - "npm:on-change@^5.0.1": "5.0.1", - "npm:zod@3.24.1": "3.24.1", - "npm:zod@^3.24.1": "3.24.1" - }, - "jsr": { - "@core/asyncutil@1.2.0": { - "integrity": "9967f15190c60df032c13f72ce5ac73d185c34f31c53dc918d8800025854c118" - }, - "@hono/hono@4.6.16": { - "integrity": "b540c6b1352d73142895f7bb6bfd0b6cc514ede61c12940338c4ae5dd06cb326" - }, - "@std/assert@1.0.10": { - "integrity": "59b5cbac5bd55459a19045d95cc7c2ff787b4f8527c0dd195078ff6f9481fbb3", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/async@1.0.9": { - "integrity": "c6472fd0623b3f3daae023cdf7ca5535e1b721dfbf376562c0c12b3fb4867f91" - }, - "@std/bytes@1.0.4": { - "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" - }, - "@std/cbor@0.1.5": { - "integrity": "abb9155998bfebfe625199e3235ae9c7357d20d19718bd81e2df883f7c06b0ce", - "dependencies": [ - "jsr:@std/bytes", - "jsr:@std/streams" - ] - }, - "@std/fmt@1.0.4": { - "integrity": "e14fe5bedee26f80877e6705a97a79c7eed599e81bb1669127ef9e8bc1e29a74" - }, - "@std/fs@1.0.9": { - "integrity": "3eef7e3ed3d317b29432c7dcb3b20122820dbc574263f721cb0248ad91bad890" - }, - "@std/internal@1.0.5": { - "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" - }, - "@std/io@0.225.0": { - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3" - }, - "@std/log@0.224.13": { - "integrity": "f04d82f676c9eb4306194ca166d296d9f1456fe4b7edf2a404a0d55c94d31df7", - "dependencies": [ - "jsr:@std/fmt", - "jsr:@std/fs", - "jsr:@std/io" - ] - }, - "@std/streams@1.0.8": { - "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3" - } - }, - "npm": { - "@ai-sdk/openai@1.1.0_zod@3.24.1": { - "integrity": "sha512-D2DaGMK89yYgO32n4Gr7gBJeJGGGS27gdfzYFMRDXlZmKh7VW1WXBp3FXxDwpmt0CgLoVI4qV8lf+gslah+kWw==", - "dependencies": [ - "@ai-sdk/provider", - "@ai-sdk/provider-utils@2.1.0_zod@3.24.1", - "zod" - ] - }, - "@ai-sdk/provider-utils@2.1.0": { - "integrity": "sha512-rBUabNoyB25PBUjaiMSk86fHNSCqTngNZVvXxv8+6mvw47JX5OexW+ZHRsEw8XKTE8+hqvNFVzctaOrRZ2i9Zw==", - "dependencies": [ - "@ai-sdk/provider", - "eventsource-parser", - "nanoid", - "secure-json-parse" - ] - }, - "@ai-sdk/provider-utils@2.1.0_zod@3.24.1": { - "integrity": "sha512-rBUabNoyB25PBUjaiMSk86fHNSCqTngNZVvXxv8+6mvw47JX5OexW+ZHRsEw8XKTE8+hqvNFVzctaOrRZ2i9Zw==", - "dependencies": [ - "@ai-sdk/provider", - "eventsource-parser", - "nanoid", - "secure-json-parse", - "zod" - ] - }, - "@ai-sdk/provider@1.0.4": { - "integrity": "sha512-lJi5zwDosvvZER3e/pB8lj1MN3o3S7zJliQq56BRr4e9V3fcRyFtwP0JRxaRS5vHYX3OJ154VezVoQNrk0eaKw==", - "dependencies": [ - "json-schema" - ] - }, - "@ai-sdk/react@1.1.0": { - "integrity": "sha512-U5lBbLyf1pw79xsk5dgHSkBv9Jta3xzWlOLpxsmHlxh1X94QOH3e1gm+nioQ/JvTuHLm23j2tz3i4MpMdchwXQ==", - "dependencies": [ - "@ai-sdk/provider-utils@2.1.0", - "@ai-sdk/ui-utils", - "swr", - "throttleit" - ] - }, - "@ai-sdk/ui-utils@1.1.0": { - "integrity": "sha512-ETXwdHaHwzC7NIehbthDFGwsTFk+gNtRL/lm85nR4WDFvvYQptoM/7wTANs0p0H7zumB3Ep5hKzv0Encu8vSRw==", - "dependencies": [ - "@ai-sdk/provider", - "@ai-sdk/provider-utils@2.1.0", - "zod-to-json-schema" - ] - }, - "@opentelemetry/api@1.9.0": { - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" - }, - "@types/diff-match-patch@1.0.36": { - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==" - }, - "@types/node@22.5.4": { - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", - "dependencies": [ - "undici-types" - ] - }, - "ai@4.1.0": { - "integrity": "sha512-95nI9hBSSAKPrMnpJbaB3yqvh+G8BS4/EtFz3HR0HgEDJpxC0R6JAlB8+B/BXHd/roNGBrS08Z3Zain/6OFSYA==", - "dependencies": [ - "@ai-sdk/provider", - "@ai-sdk/provider-utils@2.1.0", - "@ai-sdk/react", - "@ai-sdk/ui-utils", - "@opentelemetry/api", - "jsondiffpatch" - ] - }, - "chalk@5.4.1": { - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" - }, - "dequal@2.0.3": { - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" - }, - "diff-match-patch@1.0.5": { - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" - }, - "eventsource-parser@3.0.0": { - "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==" - }, - "json-schema@0.4.0": { - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "jsondiffpatch@0.6.0": { - "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", - "dependencies": [ - "@types/diff-match-patch", - "chalk", - "diff-match-patch" - ] - }, - "nanoid@3.3.8": { - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" - }, - "on-change@5.0.1": { - "integrity": "sha512-n7THCP7RkyReRSLkJb8kUWoNsxUIBxTkIp3JKno+sEz6o/9AJ3w3P9fzQkITEkMwyTKJjZciF3v/pVoouxZZMg==" - }, - "react@19.0.0": { - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==" - }, - "secure-json-parse@2.7.0": { - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" - }, - "swr@2.3.0_react@19.0.0": { - "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==", - "dependencies": [ - "dequal", - "react", - "use-sync-external-store" - ] - }, - "throttleit@2.1.0": { - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==" - }, - "undici-types@6.19.8": { - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - }, - "use-sync-external-store@1.4.0_react@19.0.0": { - "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", - "dependencies": [ - "react" - ] - }, - "zod-to-json-schema@3.24.1_zod@3.24.1": { - "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", - "dependencies": [ - "zod" - ] - }, - "zod@3.24.1": { - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==" - } - }, - "redirects": { - "https://esm.sh/ai@^4.1.0": "https://esm.sh/ai@4.1.0", - "https://esm.sh/ai@^4.1.0%22": "https://esm.sh/ai@4.1.0", - "https://esm.sh/ai@^4.1.0&deps=zod@3.24.1": "https://esm.sh/ai@4.1.0&deps=zod@3.24.1", - "https://esm.sh/eventsource-parser@^3.0.0/stream?target=denonext": "https://esm.sh/eventsource-parser@3.0.0/stream?target=denonext", - "https://esm.sh/nanoid@^3.3.8/non-secure?target=denonext": "https://esm.sh/nanoid@3.3.8/non-secure?target=denonext", - "https://esm.sh/secure-json-parse@^2.7.0?target=denonext": "https://esm.sh/secure-json-parse@2.7.0?target=denonext", - "https://esm.sh/zod-to-json-schema@^3.24.1?deps=zod@3.24.1&target=denonext": "https://esm.sh/zod-to-json-schema@3.24.1?deps=zod@3.24.1&target=denonext", - "https://esm.sh/zod-to-json-schema@^3.24.1?target=denonext": "https://esm.sh/zod-to-json-schema@3.24.1?target=denonext", - "https://esm.sh/zod@^3.0.0?target=denonext": "https://esm.sh/zod@3.24.1?target=denonext", - "https://esm.sh/zod@^3.24.1?target=denonext": "https://esm.sh/zod@3.24.1?target=denonext" - }, - "remote": { - "https://esm.sh/@ai-sdk/provider-utils@2.1.0/X-ZHpvZEAzLjI0LjE/denonext/provider-utils.mjs": "9856d401d137980ee36ef5501c444cfae2b998669e463795da4c2588f2684cb5", - "https://esm.sh/@ai-sdk/provider-utils@2.1.0/denonext/provider-utils.mjs": "9856d401d137980ee36ef5501c444cfae2b998669e463795da4c2588f2684cb5", - "https://esm.sh/@ai-sdk/provider@1.0.4/denonext/provider.mjs": "e6f4c469189a029302f175a1f537d961adf2c6dddd032cf3171d2f336513ebaa", - "https://esm.sh/@ai-sdk/ui-utils@1.1.0/X-ZHpvZEAzLjI0LjE/denonext/ui-utils.mjs": "eb45baf77e43ee2c667a382e8f80a59e8f487faf4b54425ed1e384cdbaff2c4d", - "https://esm.sh/@ai-sdk/ui-utils@1.1.0/denonext/ui-utils.mjs": "38af5455f2378ad61f8ccae077ba91ea7106dd5e951a03a41b9edfaca60c5819", - "https://esm.sh/@opentelemetry/api@1.9.0/denonext/api.mjs": "d9ede8bd549446ae09ea5af32de6b8b7d4ca13bcbd90eb39ae890c0fea78f618", - "https://esm.sh/ai@4.1.0": "2b2ae1fcad7b3639ddd08e2c4637fc0beb8132b161d8e31437969e8764430e22", - "https://esm.sh/ai@4.1.0&deps=zod@3.24.1": "32f541ae6572ec00c3f9c74c1a976f4f8f93ca7546608d4a31f367792a1970d0", - "https://esm.sh/ai@4.1.0/X-ZHpvZEAzLjI0LjE/denonext/ai.mjs": "fd1007d1f8b1ec4b20b83bc7a3abbe70100b8b32cd1e62452aaa559a3f378cad", - "https://esm.sh/ai@4.1.0/denonext/ai.mjs": "d1bea6d0ac9a4bbd15c8c728a79121d293d44fc0d0db80d58a10307993acbf90", - "https://esm.sh/eventsource-parser@3.0.0/denonext/eventsource-parser.mjs": "cc472edca63f2c005be63c35c6e1703c86fa231619e872f4de0d0db776ccb1fb", - "https://esm.sh/eventsource-parser@3.0.0/denonext/stream.mjs": "898fcadde111c0feb841bbe64e0dca8ed6f360ef177d7b29e093b1ad6bf9c500", - "https://esm.sh/eventsource-parser@3.0.0/stream?target=denonext": "6bcb7611e1dab51cded9716580ecb40f208d89f5085d061be053d4e96c1e9f3e", - "https://esm.sh/json-schema@0.4.0": "21a68e4e83b0441c3a90fa051c6b7c42d29926e4ff429745eebec153ac53cd7a", - "https://esm.sh/json-schema@0.4.0/denonext/json-schema.mjs": "95225c6fc709935fca00cfc112f2ab043d204f7bd07a8aaec75b1b5f24ca8bf6", - "https://esm.sh/nanoid@3.3.8/denonext/non-secure.mjs": "fddf4ab6510fe8b600cfa3fa6a38b05e275757135a156142d470fc7e544a600f", - "https://esm.sh/nanoid@3.3.8/non-secure?target=denonext": "2c3c59d8330ed3c0444aebb4611325a49feec485e1e2c34af6971ead1d10e7d9", - "https://esm.sh/secure-json-parse@2.7.0/denonext/secure-json-parse.mjs": "bb9840bcb6fdce5857d7412b4c4e272b70044efac66a58cc78d8c97bdb56786d", - "https://esm.sh/secure-json-parse@2.7.0?target=denonext": "00b85213b6c18313c3de9c07941e27f9666f8c601818c221775a4f744430f89f", - "https://esm.sh/zod-to-json-schema@3.24.1/X-ZHpvZEAzLjI0LjE/denonext/zod-to-json-schema.mjs": "6d4e0f3978f3d1d8786a79970b518c4d7f61aabd711de2ae4b6f977f4705c3fc", - "https://esm.sh/zod-to-json-schema@3.24.1/denonext/zod-to-json-schema.mjs": "e06c331ee78571bd1231453c23cfb6ffdb66b421fa2be04abe093f0478a894ca", - "https://esm.sh/zod-to-json-schema@3.24.1?deps=zod@3.24.1&target=denonext": "f3a2cc2162b638b5f1be693e15d6117307b99bad349fe6c077db21eac8839b06", - "https://esm.sh/zod-to-json-schema@3.24.1?target=denonext": "81ec2eb2fb6ec876c9cae8fdbebf2cf7d28170b742c5b63fe63572ce594b9bea", - "https://esm.sh/zod@3.24.0": "f3c6e7942a1d315f598aea762605e1dd494e7b6a131329ad0af5bece112fd5b4", - "https://esm.sh/zod@3.24.1": "d8f5c6cf2c4ed543003211dcd9278e94544788f5cb36549ee0490dd27238fe82", - "https://esm.sh/zod@3.24.1/denonext/zod.mjs": "32cf72ac44d794592028cb8e423d1f0c80525c49acf01f702c7dc6195b94eda3", - "https://esm.sh/zod@3.24.1?target=denonext": "d8f5c6cf2c4ed543003211dcd9278e94544788f5cb36549ee0490dd27238fe82" - }, - "workspace": { - "dependencies": [ - "jsr:@core/asyncutil@^1.2.0", - "jsr:@cross/env@^1.0.2", - "jsr:@hono/hono@^4.6.12", - "jsr:@std/assert@^1.0.8", - "jsr:@std/async@^1.0.9", - "jsr:@std/cbor@~0.1.3", - "jsr:@std/log@~0.224.11", - "npm:@ai-sdk/openai@^1.0.16", - "npm:@rivet-gg/api@0.0.1-rc.5", - "npm:ai@^4.1.0", - "npm:on-change@^5.0.1", - "npm:zod@3.24.1" - ] - } -} diff --git a/examples/javascript/ai-agent-shopper/package.json b/examples/javascript/ai-agent-shopper/package.json new file mode 100644 index 0000000000..a7f0517fd9 --- /dev/null +++ b/examples/javascript/ai-agent-shopper/package.json @@ -0,0 +1,16 @@ +{ + "name": "ai-agent-shopper", + "version": "1.0.0", + "private": true, + "dependencies": { + "@ai-sdk/openai": "^1.1.0", + "@clack/prompts": "^0.9.1", + "@rivet-gg/actor": "workspace:*", + "@std/assert": "npm:@jsr/std__assert@^1.0.10", + "ai": "^4.1.0", + "zod": "^3.24.1" + }, + "devDependencies": { + "@rivet-gg/actor-client": "workspace:*" + } +} diff --git a/examples/javascript/ai-agent-shopper/shopper_agent.ts b/examples/javascript/ai-agent-shopper/shopper_agent.ts index 542711affb..91dcdc55b8 100644 --- a/examples/javascript/ai-agent-shopper/shopper_agent.ts +++ b/examples/javascript/ai-agent-shopper/shopper_agent.ts @@ -6,40 +6,42 @@ import { UserError, } from "@rivet-gg/actor"; import { assert } from "@std/assert"; -import { CATALOG, type CatalogItem } from "./catalog.ts"; - -// HACK: `ai` package is not currently packaged correctly. Using esm.sh as a fallback. -// -// Zod must be the exact same package or else we'll get a "took too long to evaluate" error. +import { type CoreMessage, streamText, tool } from "ai"; +import { z } from "zod"; import { - type CoreMessage, - streamText, - tool, -} from "https://esm.sh/ai@^4.1.0&deps=zod@3.24.1"; -import { z } from "https://esm.sh/zod@3.24.1"; - -const SYSTEM_PROMPT = ` -You are ShopperAgent, an intelligent shopping assistant designed to interact with users and manage their shopping experience. Your primary responsibilities include updating the shopping cart, retrieving the current shopping cart status, and providing information about the available catalog of items. You are powered by the GPT-4-turbo model and utilize various tools to perform your tasks effectively. - -Capabilities: -1. **Update Shopping Cart**: You can update the quantity of items in the user's shopping cart. You require the item's slug and the desired count to perform this action. -2. **Retrieve Shopping Cart**: You can provide the current status of the user's shopping cart, detailing the items and their quantities. -3. **Access Catalog**: You can access and provide information about the catalog of available items. - -Constraints: -- You must ensure that any updates to the shopping cart are accurately reflected and communicated to the user. -- You should maintain a history of interactions to improve user experience and provide context for ongoing conversations. -- You should handle all operations asynchronously and ensure that responses are prompt and informative. - -Interaction Guidelines: -- Always confirm actions taken, such as updates to the shopping cart, with a clear and concise message. -- Provide detailed information when retrieving the shopping cart or catalog, ensuring the user has all necessary details. -- Maintain a friendly and helpful tone, ensuring the user feels supported throughout their shopping experience. - -Remember, your goal is to enhance the user's shopping experience by providing efficient and accurate assistance. Always prioritize user satisfaction and clarity in your interactions. + type CatalogItem, + getCatalogItemBySlug, + keywords as searchCatalogByKeywords, +} from "./catalog"; + +const SYSTEM_PROMPT = `You are a helpful hardware store shopping assistant. Your role is to help customers find and purchase items from our catalog. + +Key responsibilities: +- Use the searchCatalog tool with relevant tags to find items that match the customer's needs +- When searching, include common synonyms and related terms for more complete results (e.g. "saw" should include "cutting", "blade"; "hammer" should include "mallet", "striking") +- Help customers add items to their cart using updateItemsInCart +- Check the current cart contents using getShoppingCart when needed +- Only recommend items that are confirmed to exist in the catalog search results +- Ask for clarification if the customer's request is ambiguous + +Important restrictions: +- Never make assumptions about item availability - always search the catalog first +- You are not able to list the entire catalog, but you can search by keywords +- Only suggest items that are explicitly returned by the searchCatalog tool +- If a requested item cannot be found, explain this to the customer and offer to search for alternatives +- Do not make up prices, specifications, or details about items +- If unsure about a specific item, ask the customer for more details to refine the search + +When searching: +1. Break down the customer's request into relevant search tags +2. Use searchCatalog to find matching items +3. Review the search results and recommend appropriate items +4. Help add selected items to cart with specific quantities + +Always maintain a helpful and professional tone while staying strictly within the bounds of available catalog items. `; -interface ShoppingCartItem { +export interface ShoppingCartItem { slug: string; count: number; } @@ -63,6 +65,14 @@ interface ConnState { export default class ShopperAgent extends Actor { #isStreaming = false; + constructor() { + super({ + rpc: { + timeout: 60_000, + }, + }); + } + override _onInitialize(): State { return { shoppingCart: [], messages: [] }; } @@ -84,7 +94,7 @@ export default class ShopperAgent extends Actor { updateItemInCart(_rpc: Rpc, slug: string, count: number) { // Assert that the item is in the catalog - const isInCatalog = CATALOG.some((item) => item.slug === slug); + const isInCatalog = getCatalogItemBySlug(slug); assert(isInCatalog, `Item with slug ${slug} is not in the catalog`); const itemIndex = this._state.shoppingCart.findIndex( @@ -108,8 +118,11 @@ export default class ShopperAgent extends Actor { return this._state.shoppingCart; } - getCatalog(_rpc: Rpc): CatalogItem[] { - return CATALOG; + async searchCatalog( + _rpc: Rpc, + keywords: string[], + ): Promise { + return searchCatalogByKeywords(keywords); } async processPrompt(rpc: Rpc, prompt: string) { @@ -118,12 +131,19 @@ export default class ShopperAgent extends Actor { assert(!this.#isStreaming, "already streaming response"); this.#isStreaming = true; + this._state.messages.push({ + role: "user", + content: prompt, + }); + + this._log.info("prompt start", { prompt }); + const startTime = performance.now(); const { textStream } = streamText({ model: rpc.connection.state.openai("gpt-4o"), system: SYSTEM_PROMPT, messages: this._state.messages, - prompt, maxSteps: 5, + maxTokens: 16_000, tools: { updateItemsInCart: tool({ description: @@ -134,6 +154,15 @@ export default class ShopperAgent extends Actor { z.object({ slug: z .string() + .refine( + (slug) => + getCatalogItemBySlug( + slug, + ) !== undefined, + (slug) => ({ + message: `Invalid catalog item: ${slug}`, + }), + ) .describe( "The slug of the item to update in the cart", ), @@ -168,24 +197,69 @@ export default class ShopperAgent extends Actor { return `Current shopping cart: ${JSON.stringify(cart)}`; }, }), - getCatalog: tool({ - description: "Get the catalog of items", - parameters: z.object({}), - execute: async () => { - return `Catalog: ${JSON.stringify(this.getCatalog(rpc))}`; + searchCatalog: tool({ + description: "Search the catalog of items using tags", + parameters: z.object({ + tags: z + .array(z.string()) + .describe( + "Array of tags to search for in the catalog", + ), + }), + execute: async ({ tags }) => { + const results = await this.searchCatalog(rpc, tags); + return `Search results for tags [${tags.join(", ")}]: ${JSON.stringify(results)}`; + }, + }), + getCatalogItems: tool({ + description: + "Look up specific catalog items by their slugs", + parameters: z.object({ + slugs: z + .array(z.string()) + .describe("Array of item slugs to look up"), + }), + execute: async ({ slugs }) => { + const items = slugs + .map((slug) => getCatalogItemBySlug(slug)) + .filter( + (item): item is CatalogItem => + item !== undefined, + ); + return `Items found: ${JSON.stringify(items)}`; }, }), }, + onStepFinish: ({ + text, + stepType, + finishReason, + toolCalls, + toolResults, + }) => { + this._log.info("prompt step finish", { + text, + stepType, + finishReason, + toolCalls: toolCalls.map(({ toolName, args }) => ({ + name: toolName, + args, + })), + toolResults, + }); + }, onFinish: ({ text }) => { // Save response - this._state.messages.push({ - role: "user", - content: prompt, - }); this._state.messages.push({ role: "system", content: text, }); + + const elapsed = performance.now() - startTime; + this._log.info("prompt finish", { + text, + elapsed: (elapsed / 1000).toFixed(3), + }); }, }); diff --git a/examples/javascript/ai-agent-shopper/shopper_agent_test.ts b/examples/javascript/ai-agent-shopper/shopper_agent_test.ts index dc1b0abb4c..9f0e77bb35 100644 --- a/examples/javascript/ai-agent-shopper/shopper_agent_test.ts +++ b/examples/javascript/ai-agent-shopper/shopper_agent_test.ts @@ -1,31 +1,53 @@ import { TestClient } from "@rivet-gg/actor-client/test"; -import type ShopperAgent from "./shopper_agent.ts"; +import { + assertEquals, + assertExists, + assertObjectMatch, + assertThrows, +} from "@std/assert"; +import type ShopperAgent from "./shopper_agent"; -Deno.test("shopper agent updates and retrieves shopping cart", async () => { - const client = new TestClient(); +const client = new TestClient(); - // Get-or-create a shopper agent actor - const shopperAgent = await client.get({ +// Get-or-create a shopper agent actor +const openaiKey = Deno.env.get("OPENAI_KEY"); +assertExists(openaiKey); +const shopperAgent = await client.get( + { name: "shopper_agent", - }); + }, + { + parameters: { + openaiKey, + }, + }, +); - // // Test updating the shopping cart - // await shopperAgent.processPrompt("Please update the shopping cart with 2 items of white paint."); - // const cart = await shopperAgent.getShoppingCart(); - // assertEquals(cart, [{ slug: "paint-white", count: 2 }]); +// Test updating the shopping cart +await shopperAgent.processPrompt( + "Please update the shopping cart with 2 items of white paint.", +); +const cart = await shopperAgent.getShoppingCart(); +console.log("Cart", cart); +assertEquals(cart.length, 1); +assertObjectMatch(cart[0], { slug: "paint-white", count: 2 }); - // // Test retrieving the catalog - // const catalog = await shopperAgent.processPrompt("Can you provide the current catalog?"); +// Test retrieving the catalog +const catalog = await shopperAgent.processPrompt( + "Can you provide the current catalog?", +); +console.log("Catalog", catalog); - // // Test updating with an invalid item - // assertThrows( - // async () => { - // await shopperAgent.processPrompt("Please add 1 item with slug 'invalid-slug' to the shopping cart."); - // }, - // Error, - // "Item with slug invalid-slug is not in the catalog", - // ); +// Test updating with an invalid item +assertThrows( + async () => { + await shopperAgent.processPrompt( + "Please add 1 item with slug 'invalid-slug' to the shopping cart.", + ); + }, + Error, + "Item with slug invalid-slug is not in the catalog", +); - // Disconnect from the actor when finished - await shopperAgent.disconnect(); -}); +// Disconnect from the actor when finished +await shopperAgent.disconnect(); diff --git a/examples/javascript/ai-agent-shopper/tsconfig.json b/examples/javascript/ai-agent-shopper/tsconfig.json new file mode 100644 index 0000000000..2782b3532a --- /dev/null +++ b/examples/javascript/ai-agent-shopper/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": false, + "stripInternal": true, + "moduleResolution": "bundler", + "types": ["node"], + "lib": ["ESNext", "DOM"] + }, + "include": ["**/*.ts"] +} diff --git a/examples/javascript/counter/counter.ts b/examples/javascript/counter/counter.ts index 3a8220484a..9751f3a6e8 100644 --- a/examples/javascript/counter/counter.ts +++ b/examples/javascript/counter/counter.ts @@ -1,4 +1,6 @@ +// @ts-types="../../../sdks/actor/runtime/dist/mod.d.ts" import { Actor } from "@rivet-gg/actor"; +// @ts-types="../../../sdks/actor/runtime/dist/mod.d.ts" import type { Rpc } from "@rivet-gg/actor"; // Durable state for the counter (https://rivet.gg/docs/state) @@ -12,6 +14,10 @@ export default class Counter extends Actor { return { count: 0 }; } + override _onStart() { + console.log("starting"); + } + // Listen for state changes (https://rivet.gg/docs/lifecycle) override _onStateChange(newState: State): void | Promise { // Broadcast a state update event to all clients (https://rivet.gg/docs/events) diff --git a/examples/javascript/counter/counter_test.ts b/examples/javascript/counter/counter_test.ts index b24ad7e2f6..b41100b11a 100644 --- a/examples/javascript/counter/counter_test.ts +++ b/examples/javascript/counter/counter_test.ts @@ -2,22 +2,18 @@ import { TestClient } from "@rivet-gg/actor-client/test"; import { assertEquals } from "@std/assert"; import type Counter from "./counter.ts"; -Deno.test("counter increments", async () => { - const client = new TestClient(); +const client = new TestClient(); - // Get-or-create a counter actor - const counter = await client.get({ name: "counter" }); +// Get-or-create a counter actor +const counter = await client.get({ name: "counter" }); - // Listen for update count events (https://rivet.gg/docs/events) - counter.on("countUpdate", (count: number) => - console.log("New count:", count), - ); +// Listen for update count events (https://rivet.gg/docs/events) +counter.on("countUpdate", (count: number) => console.log("New count:", count)); - // Increment the count over remote procedure call (https://rivet.gg/docs/rpc) - const count1 = await counter.increment(1); - const count2 = await counter.increment(2); - assertEquals(count2, count1 + 2); +// Increment the count over remote procedure call (https://rivet.gg/docs/rpc) +const count1 = await counter.increment(1); +const count2 = await counter.increment(2); +assertEquals(count2, count1 + 2); - // Disconnect from the actor when finished (https://rivet.gg/docs/connections) - await counter.disconnect(); -}); +// Disconnect from the actor when finished (https://rivet.gg/docs/connections) +await counter.disconnect(); diff --git a/examples/javascript/counter/deno.jsonc b/examples/javascript/counter/deno.jsonc deleted file mode 100644 index eca0be26b8..0000000000 --- a/examples/javascript/counter/deno.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "imports": { - "@core/asyncutil": "jsr:@core/asyncutil@^1.2.0", - "@cross/env": "jsr:@cross/env@^1.0.2", - "@rivet-gg/api": "npm:@rivet-gg/api@0.0.1-rc.5", - "@std/assert": "jsr:@std/assert@^1.0.8", - "@std/async": "jsr:@std/async@^1.0.9", - "@std/cbor": "jsr:@std/cbor@^0.1.3", - "@std/log": "jsr:@std/log@^0.224.11", - "hono": "jsr:@hono/hono@^4.6.12", - "@rivet-gg/actor-core": "../../../sdks/actor/core/src/mod.ts", - "@rivet-gg/actor": "../../../sdks/actor/runtime/src/mod.ts", - "@rivet-gg/actor-client": "../../../sdks/actor/client/src/mod.ts", - "@rivet-gg/actor-client/test": "../../../sdks/actor/client/src/test.ts", - "on-change": "npm:on-change@^5.0.1", - "zod": "npm:zod@^3.24.1" - }, - "fmt": { - "useTabs": true - } -} diff --git a/examples/javascript/counter/deno.lock b/examples/javascript/counter/deno.lock deleted file mode 100644 index f28d166b5b..0000000000 --- a/examples/javascript/counter/deno.lock +++ /dev/null @@ -1,341 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@core/asyncutil@^1.2.0": "1.2.0", - "jsr:@cross/deepmerge@1": "1.0.0", - "jsr:@cross/env@^1.0.2": "1.0.2", - "jsr:@cross/runtime@1": "1.1.0", - "jsr:@hono/hono@^4.6.12": "4.6.12", - "jsr:@std/assert@^1.0.8": "1.0.8", - "jsr:@std/async@^1.0.9": "1.0.9", - "jsr:@std/bytes@^1.0.4": "1.0.4", - "jsr:@std/cbor@~0.1.3": "0.1.3", - "jsr:@std/fmt@^1.0.3": "1.0.3", - "jsr:@std/fs@^1.0.6": "1.0.6", - "jsr:@std/internal@^1.0.5": "1.0.5", - "jsr:@std/io@0.225": "0.225.0", - "jsr:@std/log@~0.224.11": "0.224.11", - "jsr:@std/streams@^1.0.8": "1.0.8", - "npm:@rivet-gg/api@0.0.1-rc.5": "0.0.1-rc.5", - "npm:on-change@^5.0.1": "5.0.1", - "npm:zod@^3.24.1": "3.24.1" - }, - "jsr": { - "@core/asyncutil@1.2.0": { - "integrity": "9967f15190c60df032c13f72ce5ac73d185c34f31c53dc918d8800025854c118" - }, - "@cross/deepmerge@1.0.0": { - "integrity": "1e1318a74e31ba1959b9aa0acae8bd417b806f74ffd25ac07c90e12f83ad6b1d" - }, - "@cross/env@1.0.2": { - "integrity": "28501ad1043c218a5b00fe5db27ec62c01ab16371bbe1b9d738496f0a7c5eeb8", - "dependencies": [ - "jsr:@cross/deepmerge", - "jsr:@cross/runtime" - ] - }, - "@cross/runtime@1.1.0": { - "integrity": "f35a3b768a9de125277329483b684062ffc9ee86f4449cb8b3d614adcad64ffb" - }, - "@hono/hono@4.6.12": { - "integrity": "fa0b97fa7c3292f0d9957109ac07a475fe485868795b71b8e3114c284152cdb5" - }, - "@std/assert@1.0.8": { - "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/async@1.0.9": { - "integrity": "c6472fd0623b3f3daae023cdf7ca5535e1b721dfbf376562c0c12b3fb4867f91" - }, - "@std/bytes@1.0.4": { - "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" - }, - "@std/cbor@0.1.3": { - "integrity": "aa38d01d568326e5c45c7519ce23cba201c2ca3285a512fe316ebb65230e1831", - "dependencies": [ - "jsr:@std/bytes", - "jsr:@std/streams" - ] - }, - "@std/fmt@1.0.3": { - "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" - }, - "@std/fs@1.0.6": { - "integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2" - }, - "@std/internal@1.0.5": { - "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" - }, - "@std/io@0.225.0": { - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3" - }, - "@std/log@0.224.11": { - "integrity": "df5e5a6d6ab8bcea016a17982cd2435f65234d6618bf631925587c0b2eae2a4e", - "dependencies": [ - "jsr:@std/fmt", - "jsr:@std/fs", - "jsr:@std/io" - ] - }, - "@std/streams@1.0.8": { - "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3" - } - }, - "npm": { - "@rivet-gg/api@0.0.1-rc.5": { - "integrity": "sha512-InaqqAWBW8e3tOEe0OpeEMlP2MctmVrLrzHjkNtYbmrxMW+2ZKHxbqk/pd9DyOuaRCCLr5N1X0HLExOR7EZaTA==", - "dependencies": [ - "@types/readable-stream", - "form-data", - "js-base64", - "node-fetch", - "qs", - "readable-stream", - "url-join" - ] - }, - "@types/node@22.5.4": { - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", - "dependencies": [ - "undici-types" - ] - }, - "@types/readable-stream@4.0.18": { - "integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==", - "dependencies": [ - "@types/node", - "safe-buffer@5.1.2" - ] - }, - "abort-controller@3.0.0": { - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": [ - "event-target-shim" - ] - }, - "asynckit@0.4.0": { - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "base64-js@1.5.1": { - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "buffer@6.0.3": { - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dependencies": [ - "base64-js", - "ieee754" - ] - }, - "call-bind-apply-helpers@1.0.1": { - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dependencies": [ - "es-errors", - "function-bind" - ] - }, - "call-bind@1.0.8": { - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dependencies": [ - "call-bind-apply-helpers", - "es-define-property", - "get-intrinsic", - "set-function-length" - ] - }, - "combined-stream@1.0.8": { - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": [ - "delayed-stream" - ] - }, - "define-data-property@1.1.4": { - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": [ - "es-define-property", - "es-errors", - "gopd" - ] - }, - "delayed-stream@1.0.0": { - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "dunder-proto@1.0.0": { - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", - "dependencies": [ - "call-bind-apply-helpers", - "es-errors", - "gopd" - ] - }, - "es-define-property@1.0.1": { - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" - }, - "es-errors@1.3.0": { - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "event-target-shim@5.0.1": { - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "events@3.3.0": { - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "form-data@4.0.1": { - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dependencies": [ - "asynckit", - "combined-stream", - "mime-types" - ] - }, - "function-bind@1.1.2": { - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "get-intrinsic@1.2.5": { - "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", - "dependencies": [ - "call-bind-apply-helpers", - "dunder-proto", - "es-define-property", - "es-errors", - "function-bind", - "gopd", - "has-symbols", - "hasown" - ] - }, - "gopd@1.2.0": { - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" - }, - "has-property-descriptors@1.0.2": { - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": [ - "es-define-property" - ] - }, - "has-symbols@1.1.0": { - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" - }, - "hasown@2.0.2": { - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": [ - "function-bind" - ] - }, - "ieee754@1.2.1": { - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "js-base64@3.7.7": { - "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" - }, - "mime-db@1.52.0": { - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types@2.1.35": { - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": [ - "mime-db" - ] - }, - "node-fetch@2.7.0": { - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": [ - "whatwg-url" - ] - }, - "object-inspect@1.13.3": { - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" - }, - "on-change@5.0.1": { - "integrity": "sha512-n7THCP7RkyReRSLkJb8kUWoNsxUIBxTkIp3JKno+sEz6o/9AJ3w3P9fzQkITEkMwyTKJjZciF3v/pVoouxZZMg==" - }, - "process@0.11.10": { - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "qs@6.13.1": { - "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", - "dependencies": [ - "side-channel" - ] - }, - "readable-stream@4.5.2": { - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": [ - "abort-controller", - "buffer", - "events", - "process", - "string_decoder" - ] - }, - "safe-buffer@5.1.2": { - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-buffer@5.2.1": { - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "set-function-length@1.2.2": { - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": [ - "define-data-property", - "es-errors", - "function-bind", - "get-intrinsic", - "gopd", - "has-property-descriptors" - ] - }, - "side-channel@1.0.6": { - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": [ - "call-bind", - "es-errors", - "get-intrinsic", - "object-inspect" - ] - }, - "string_decoder@1.3.0": { - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": [ - "safe-buffer@5.2.1" - ] - }, - "tr46@0.0.3": { - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "undici-types@6.19.8": { - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - }, - "url-join@5.0.0": { - "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==" - }, - "webidl-conversions@3.0.1": { - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url@5.0.0": { - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": [ - "tr46", - "webidl-conversions" - ] - }, - "zod@3.24.1": { - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==" - } - }, - "workspace": { - "dependencies": [ - "jsr:@core/asyncutil@^1.2.0", - "jsr:@cross/env@^1.0.2", - "jsr:@hono/hono@^4.6.12", - "jsr:@std/assert@^1.0.8", - "jsr:@std/async@^1.0.9", - "jsr:@std/cbor@~0.1.3", - "jsr:@std/log@~0.224.11", - "npm:@rivet-gg/api@0.0.1-rc.5", - "npm:on-change@^5.0.1", - "npm:zod@^3.24.1" - ] - } -} diff --git a/examples/javascript/counter/package.json b/examples/javascript/counter/package.json new file mode 100644 index 0000000000..81ecfd2f13 --- /dev/null +++ b/examples/javascript/counter/package.json @@ -0,0 +1,11 @@ +{ + "name": "counter", + "version": "1.0.0", + "private": true, + "dependencies": { + "@rivet-gg/actor": "workspace:*" + }, + "devDependencies": { + "@rivet-gg/actor-client": "workspace:*" + } +} diff --git a/examples/javascript/counter/rivet.jsonc b/examples/javascript/counter/rivet.jsonc index 0b51468f71..0a85bfc8a3 100644 --- a/examples/javascript/counter/rivet.jsonc +++ b/examples/javascript/counter/rivet.jsonc @@ -2,7 +2,11 @@ "builds": { "counter": { "script": "counter.ts", - "access": "public" + "access": "public", + "unstable": { + "minify": false, + "dump_build": true + } } }, "unstable": { diff --git a/package.json b/package.json index 063de64b7d..6148063729 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "frontend/apps/*", "frontend/packages/*", "site", - "sdks/actor/*" + "sdks/actor/*", + "packages/toolchain/js-utils-embed/js", + "examples/javascript/*" ], "scripts": { "start": "npx turbo dev", diff --git a/packages/toolchain/actors-sdk-embed/Cargo.toml b/packages/toolchain/actors-sdk-embed/Cargo.toml index 73d198a9cb..65c579c11c 100644 --- a/packages/toolchain/actors-sdk-embed/Cargo.toml +++ b/packages/toolchain/actors-sdk-embed/Cargo.toml @@ -20,3 +20,6 @@ sha2 = "0.10.8" tempfile = "3.13.0" tokio = { version = "1.40.0", default-features = false, features = ["fs", "rt-multi-thread"] } walkdir = "2.5.0" +rivet-js-utils-embed.workspace = true +serde_json = "1.0.137" + diff --git a/packages/toolchain/actors-sdk-embed/build.rs b/packages/toolchain/actors-sdk-embed/build.rs index fc454af653..f81f87c2de 100644 --- a/packages/toolchain/actors-sdk-embed/build.rs +++ b/packages/toolchain/actors-sdk-embed/build.rs @@ -1,9 +1,10 @@ use anyhow::*; -use fs_extra::dir::{copy, CopyOptions}; use merkle_hash::MerkleTree; +use serde_json::json; use std::{ fs, path::{Path, PathBuf}, + process::Stdio, }; #[tokio::main] @@ -14,30 +15,37 @@ async fn main() -> Result<()> { let sdk_path = PathBuf::from(manifest_dir.clone()).join("../../../sdks/actor"); // Copy SDK directory to out_dir - let out_sdk_path = Path::new(&out_dir).join("actor-sdk"); + let dist_path = Path::new(&out_dir).join("actor-sdk"); // Remove old dir - if out_sdk_path.is_dir() { - fs::remove_dir_all(&out_sdk_path).context("fs::remove_dir_all")?; + if dist_path.is_dir() { + fs::remove_dir_all(&dist_path).context("fs::remove_dir_all")?; } - // Copy sdk directory to out_dir - let mut copy_options = CopyOptions::new(); - copy_options.overwrite = true; - copy_options.copy_inside = true; - copy(&sdk_path, &out_sdk_path, ©_options).with_context(|| { - format!( - "failed to copy directory from {} to {}", - sdk_path.display(), - out_sdk_path.display() - ) - })?; + // Build manager + build_backend_command_raw(CommandOpts { + task_path: "src/tasks/build/mod.js", + input: json!({ + "entryPoint": sdk_path.join("manager/src/mod.ts"), + "outDir": dist_path.join("manager"), + "bundle": { + "minify": true, + "analyzeResult": false, + "logLevel": "debug" + } + }), + current_dir: sdk_path.clone(), + }) + .await?; println!("cargo:rerun-if-changed={}", sdk_path.display()); - println!("cargo:rustc-env=ACTOR_SDK_PATH={}", out_sdk_path.display()); println!( - "cargo:rustc-env=ACTOR_SDK_HASH={}", - hash_directory(&out_sdk_path)? + "cargo:rustc-env=ACTOR_SDK_DIST_PATH={}", + dist_path.display() + ); + println!( + "cargo:rustc-env=ACTOR_SDK_DIST_HASH={}", + hash_directory(&dist_path)? ); Ok(()) @@ -55,3 +63,45 @@ fn hash_directory>(path: P) -> Result { .join(""); Ok(hash) } + +pub struct CommandOpts { + pub task_path: &'static str, + pub input: serde_json::Value, + pub current_dir: PathBuf, +} + +// TODO: Split toolchain's js_utils in to a separate crate so we can share this code +pub async fn build_backend_command_raw(opts: CommandOpts) -> Result<()> { + let data_dir = tempfile::tempdir()?; + + // Get Deno executable + let deno = deno_embed::get_executable(&data_dir.path().to_owned()).await?; + + // Get JS utils + let base = rivet_js_utils_embed::dist_path(&data_dir.path().to_owned()).await?; + + // Serialize command + let input_json = serde_json::to_string(&opts.input)?; + + // Run backend + let status = tokio::process::Command::new(deno.executable_path) + .args([ + "run", + "--quiet", + "--no-check", + "--allow-all", + "--unstable-bare-node-builtins", + ]) + .arg(base.join(opts.task_path)) + .arg("--input") + .arg(input_json) + .env("DENO_NO_UPDATE_CHECK", "1") + .current_dir(opts.current_dir) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .await?; + ensure!(status.success(), "command failed"); + + Ok(()) +} diff --git a/packages/toolchain/actors-sdk-embed/src/lib.rs b/packages/toolchain/actors-sdk-embed/src/lib.rs index 2409c32950..bf82648b9a 100644 --- a/packages/toolchain/actors-sdk-embed/src/lib.rs +++ b/packages/toolchain/actors-sdk-embed/src/lib.rs @@ -3,20 +3,20 @@ use include_dir::{include_dir, Dir}; use std::path::PathBuf; use tokio::fs; -const ACTOR_SDK_DIR: Dir = include_dir!("$ACTOR_SDK_PATH"); -const ACTOR_SDK_HASH: &'static str = env!("ACTOR_SDK_HASH"); +const ACTOR_SDK_DIST_DIR: Dir = include_dir!("$ACTOR_SDK_DIST_PATH"); +const ACTOR_SDK_DIST_HASH: &'static str = env!("ACTOR_SDK_DIST_HASH"); /// Return a path for the source dir. If one does not exist, the source dir will automatically be /// extracted and executables will be set. -pub async fn src_path(data_dir: &PathBuf) -> Result { - // Create path to src based on hash - let src_dir = data_dir.join("actor-sdk").join(ACTOR_SDK_HASH); +pub async fn dist_path(data_dir: &PathBuf) -> Result { + // Create path to dist based on hash + let dist_dir = data_dir.join("actor-sdk").join(ACTOR_SDK_DIST_HASH); // Write actor-sdk if does not exist - if !src_dir.exists() { - fs::create_dir_all(&src_dir).await?; - tokio::task::block_in_place(|| ACTOR_SDK_DIR.extract(&src_dir))?; + if !dist_dir.exists() { + fs::create_dir_all(&dist_dir).await?; + tokio::task::block_in_place(|| ACTOR_SDK_DIST_DIR.extract(&dist_dir))?; } - Ok(src_dir) + Ok(dist_dir) } diff --git a/packages/toolchain/js-utils-embed/build.rs b/packages/toolchain/js-utils-embed/build.rs index 4d63b45d42..6eba3d7367 100644 --- a/packages/toolchain/js-utils-embed/build.rs +++ b/packages/toolchain/js-utils-embed/build.rs @@ -15,6 +15,7 @@ async fn main() -> Result<()> { // Copy js-utils directory to out_dir let out_js_utils_path = out_dir.join("js-utils"); + let out_js_utils_path_dist = out_js_utils_path.join("dist"); // Remove old dir if out_js_utils_path.is_dir() { @@ -39,8 +40,9 @@ async fn main() -> Result<()> { // Prepare the directory for `include_dir!` let status = Command::new(&deno_exec.executable_path) .env("DENO_NO_UPDATE_CHECK", "1") - .arg("task") - .arg("prepare") + .arg("run") + .arg("-A") + .arg("build.ts") // Deno runs out of memory on Windows .env( "DENO_V8_FLAGS", @@ -49,7 +51,7 @@ async fn main() -> Result<()> { .current_dir(&out_js_utils_path) .status()?; if !status.success() { - panic!("cache dependencies failed"); + panic!("build js utils"); } // TODO: This doesn't work @@ -59,12 +61,12 @@ async fn main() -> Result<()> { println!("cargo:rerun-if-changed={}", js_utils_path.display()); println!( - "cargo:rustc-env=JS_UTILS_PATH={}", - out_js_utils_path.display() + "cargo:rustc-env=JS_UTILS_DIST_PATH={}", + out_js_utils_path_dist.display() ); println!( - "cargo:rustc-env=JS_UTILS_HASH={}", - hash_directory(&out_js_utils_path)? + "cargo:rustc-env=JS_UTILS_DIST_HASH={}", + hash_directory(&out_js_utils_path_dist)? ); Ok(()) @@ -82,56 +84,3 @@ fn hash_directory>(path: P) -> Result { .join(""); Ok(hash) } - -// fn strip_cross_platform(path: &Path) -> Result<()> { -// // Remove directories starting with "@esbuild+" -// let esbuild_path = path.join("node_modules").join(".deno"); -// let output = Command::new("find") -// .arg(&esbuild_path) -// .arg("-type") -// .arg("d") -// .arg("-name") -// .arg("@esbuild+*") -// .arg("-exec") -// .arg("rm") -// .arg("-rf") -// .arg("{}") -// .arg("+") -// .output() -// .context("Failed to execute 'find' command to remove @esbuild+ directories")?; -// -// if !output.status.success() { -// return Err(anyhow!( -// "Failed to remove @esbuild+ directories. Path: {}, Status: {}, Stdout: {}, Stderr: {}", -// esbuild_path.display(), -// output.status, -// String::from_utf8_lossy(&output.stdout), -// String::from_utf8_lossy(&output.stderr) -// )); -// } -// -// // Remove broken symlinks -// let output = Command::new("find") -// .arg(path) -// .arg("-type") -// .arg("l") -// .arg("-exec") -// .arg("sh") -// .arg("-c") -// .arg("for x; do [ -e \"$x\" ] || rm \"$x\"; done") -// .arg("{}") -// .arg("+") -// .output() -// .context("Failed to execute 'find' command to remove broken symlinks")?; -// -// if !output.status.success() { -// return Err(anyhow!( -// "Failed to remove broken symlinks. Status: {}, Stdout: {}, Stderr: {}", -// output.status, -// String::from_utf8_lossy(&output.stdout), -// String::from_utf8_lossy(&output.stderr) -// )); -// } -// -// Ok(()) -// } diff --git a/packages/toolchain/js-utils-embed/js/.gitignore b/packages/toolchain/js-utils-embed/js/.gitignore deleted file mode 100644 index 2e48fcaf2f..0000000000 --- a/packages/toolchain/js-utils-embed/js/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -node_modules/ - diff --git a/packages/toolchain/js-utils-embed/js/build.ts b/packages/toolchain/js-utils-embed/js/build.ts new file mode 100644 index 0000000000..e279b11c2f --- /dev/null +++ b/packages/toolchain/js-utils-embed/js/build.ts @@ -0,0 +1,30 @@ +import * as esbuild from "esbuild"; +import { glob } from "glob"; + +// Find all mod.ts files in src/tasks directories +const entryPoints = await glob("src/tasks/*/mod.ts"); + +// Build configuration +await esbuild.build({ + entryPoints, + outdir: "dist", + bundle: true, + sourcemap: true, + platform: "node", + format: "esm", + target: "esnext", + outbase: "./", + plugins: [ + { + name: "replace-esbuild", + setup(build) { + build.onResolve({ filter: /^esbuild$/ }, (args) => { + return { path: "npm:esbuild@^0.20.2", external: true }; + }); + }, + }, + ], + banner: { + js: 'import { createRequire } from "node:module";\nconst require = createRequire(import.meta.url);\nconst __filename = import.meta.filename;\nconst __dirname = import.meta.dirname;', + }, +}); diff --git a/packages/toolchain/js-utils-embed/js/deno.jsonc b/packages/toolchain/js-utils-embed/js/deno.jsonc deleted file mode 100644 index 1893f292bb..0000000000 --- a/packages/toolchain/js-utils-embed/js/deno.jsonc +++ /dev/null @@ -1,88 +0,0 @@ -{ - "tasks": { - // Format - "format": "deno fmt .", - "format:check": "deno fmt --check .", - - "check": "deno check src/**/*.ts", - - // Remove old vendored files in order to ensure a consistent cache. - "cache": "deno task cache:purge && deno task cache:download", - "cache:purge": "rm -rf vendor node_modules", - "cache:download": "deno cache src/**/*.ts", - - // Clear cache first in order to have as clean an environment as possible. - // Cache must be the last step in order to prevent unwanted extra cached - // files. Need to download cache before check in order to prevent error. - "prepare": "deno clean && deno task format && deno task check && deno task cache", - - "lint": "deno lint cli/**/*.ts toolchain/**/*.ts runtime/**/*.ts", - "lint:fix": "deno lint cli/**/*.ts toolchain/**/*.ts runtime/**/*.ts", - - "test:core": "deno test -A ." - }, - "lint": { - "include": ["packages/"], - "exclude": ["tests/"], - "rules": { - "exclude": ["no-empty-interface", "no-explicit-any", "require-await"] - } - }, - "fmt": { - "lineWidth": 120, - "useTabs": true - }, - "vendor": true, - "imports": { - "@asteasolutions/zod-to-openapi": "npm:@asteasolutions/zod-to-openapi@^7.1.1", - "@bartlomieju/postgres": "jsr:@bartlomieju/postgres@^0.17.2", - "@cross/dir": "jsr:@cross/dir@^1.1.0", - "@hono/hono": "jsr:@hono/hono@^4.6.3", - "@luca/esbuild-deno-loader": "jsr:@rivet-gg/esbuild-deno-loader@^0.10.3-fork.3", - "@std/assert": "jsr:@std/assert@0.213", - "@std/async": "jsr:@std/async@^1.0.4", - "@std/cli": "jsr:@std/cli@^1.0.5", - "@std/collections": "jsr:@std/collections@^1.0.5", - "@std/crypto": "jsr:@std/crypto@^1.0.3", - "@std/encoding": "jsr:@std/encoding@^1.0.3", - "@std/fmt": "jsr:@std/fmt@^1.0.1", - "@std/fs": "jsr:@std/fs@0.213", - "@std/jsonc": "jsr:@std/jsonc@0.213", - "@std/path": "jsr:@std/path@0.213", - "@ts-morph/ts-morph": "jsr:@ts-morph/ts-morph@^23.0.0", - "cloudflare:workers": "npm:@cloudflare/workers-types", - "dedent": "npm:dedent@^1.5.3", - "esbuild": "npm:esbuild@^0.20.2", - "esbuild-plugins-node-modules-polyfill": "npm:esbuild-plugins-node-modules-polyfill@1.6.4", - "glob": "npm:glob@^11.0.0", - "nanoevents": "npm:nanoevents@^9.0.0", - "tar": "npm:tar@^7.4.3", - "typescript-json-schema": "npm:typescript-json-schema@^0.62.0", - "x/importmap": "/Users/nathan/misc/esbuild_deno_loader/vendor/x/importmap/mod.ts", - "x/importmap/_util.ts": "/Users/nathan/misc/esbuild_deno_loader/vendor/x/importmap/_util.ts", - "zod": "npm:zod@^3.23.8", - "zod-validation-error": "npm:zod-validation-error@^3.3.1" - }, - "compilerOptions": { - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "useUnknownInCatchVariables": true, - "alwaysStrict": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "exactOptionalPropertyTypes": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - // "noPropertyAccessFromIndexSignature": true, - "allowUnusedLabels": true, - "allowUnreachableCode": true, - "noImplicitAny": true - } -} diff --git a/packages/toolchain/js-utils-embed/js/deno.lock b/packages/toolchain/js-utils-embed/js/deno.lock deleted file mode 100644 index 036ea357bc..0000000000 --- a/packages/toolchain/js-utils-embed/js/deno.lock +++ /dev/null @@ -1,261 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", - "jsr:@std/assert@~0.213.1": "0.213.1", - "jsr:@std/bytes@^1.0.2": "1.0.4", - "jsr:@std/cli@^1.0.5": "1.0.6", - "jsr:@std/encoding@^1.0.5": "1.0.6", - "jsr:@std/fmt@^1.0.1": "1.0.2", - "jsr:@std/fs@0.213": "0.213.1", - "jsr:@std/json@~0.213.1": "0.213.1", - "jsr:@std/jsonc@0.213": "0.213.1", - "jsr:@std/path@0.213": "0.213.1", - "jsr:@std/path@^1.0.6": "1.0.8", - "jsr:@std/path@~0.213.1": "0.213.1", - "npm:esbuild-plugins-node-modules-polyfill@1.6.4": "1.6.4_esbuild@0.20.2", - "npm:esbuild@~0.20.2": "0.20.2", - "npm:zod-validation-error@^3.3.1": "3.4.0_zod@3.23.8", - "npm:zod@^3.23.8": "3.23.8" - }, - "jsr": { - "@luca/esbuild-deno-loader@0.11.1": { - "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", - "dependencies": [ - "jsr:@std/bytes", - "jsr:@std/encoding", - "jsr:@std/path@^1.0.6" - ] - }, - "@std/assert@0.213.1": { - "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" - }, - "@std/bytes@1.0.4": { - "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" - }, - "@std/cli@1.0.6": { - "integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d" - }, - "@std/encoding@1.0.6": { - "integrity": "ca87122c196e8831737d9547acf001766618e78cd8c33920776c7f5885546069" - }, - "@std/fmt@1.0.2": { - "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" - }, - "@std/fs@0.213.1": { - "integrity": "fbcaf099f8a85c27ab0712b666262cda8fe6d02e9937bf9313ecaea39a22c501", - "dependencies": [ - "jsr:@std/assert", - "jsr:@std/path@~0.213.1" - ] - }, - "@std/json@0.213.1": { - "integrity": "f572b1de605d07c4a5602445dac54bfc51b1fb87a3710a17aed2608bfca54e68" - }, - "@std/jsonc@0.213.1": { - "integrity": "5578f21aa583b7eb7317eed077ffcde47b294f1056bdbb9aacec407758637bfe", - "dependencies": [ - "jsr:@std/assert", - "jsr:@std/json" - ] - }, - "@std/path@0.213.1": { - "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", - "dependencies": [ - "jsr:@std/assert" - ] - }, - "@std/path@1.0.8": { - "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" - } - }, - "npm": { - "@esbuild/aix-ppc64@0.20.2": { - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==" - }, - "@esbuild/android-arm64@0.20.2": { - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==" - }, - "@esbuild/android-arm@0.20.2": { - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==" - }, - "@esbuild/android-x64@0.20.2": { - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==" - }, - "@esbuild/darwin-arm64@0.20.2": { - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==" - }, - "@esbuild/darwin-x64@0.20.2": { - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==" - }, - "@esbuild/freebsd-arm64@0.20.2": { - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==" - }, - "@esbuild/freebsd-x64@0.20.2": { - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==" - }, - "@esbuild/linux-arm64@0.20.2": { - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==" - }, - "@esbuild/linux-arm@0.20.2": { - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==" - }, - "@esbuild/linux-ia32@0.20.2": { - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==" - }, - "@esbuild/linux-loong64@0.20.2": { - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==" - }, - "@esbuild/linux-mips64el@0.20.2": { - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==" - }, - "@esbuild/linux-ppc64@0.20.2": { - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==" - }, - "@esbuild/linux-riscv64@0.20.2": { - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==" - }, - "@esbuild/linux-s390x@0.20.2": { - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==" - }, - "@esbuild/linux-x64@0.20.2": { - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==" - }, - "@esbuild/netbsd-x64@0.20.2": { - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==" - }, - "@esbuild/openbsd-x64@0.20.2": { - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==" - }, - "@esbuild/sunos-x64@0.20.2": { - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==" - }, - "@esbuild/win32-arm64@0.20.2": { - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==" - }, - "@esbuild/win32-ia32@0.20.2": { - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==" - }, - "@esbuild/win32-x64@0.20.2": { - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==" - }, - "@jspm/core@2.1.0": { - "integrity": "sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==" - }, - "acorn@8.14.0": { - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" - }, - "confbox@0.1.8": { - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" - }, - "esbuild-plugins-node-modules-polyfill@1.6.4_esbuild@0.20.2": { - "integrity": "sha512-x3MCOvZrKDGAfqAYS/pZUUSwiN+XH7x84A+Prup0CZBJKuGfuGkTAC4g01D6JPs/GCM9wzZVfd8bmiy+cP/iXA==", - "dependencies": [ - "@jspm/core", - "esbuild", - "local-pkg", - "resolve.exports" - ] - }, - "esbuild@0.20.2": { - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "dependencies": [ - "@esbuild/aix-ppc64", - "@esbuild/android-arm", - "@esbuild/android-arm64", - "@esbuild/android-x64", - "@esbuild/darwin-arm64", - "@esbuild/darwin-x64", - "@esbuild/freebsd-arm64", - "@esbuild/freebsd-x64", - "@esbuild/linux-arm", - "@esbuild/linux-arm64", - "@esbuild/linux-ia32", - "@esbuild/linux-loong64", - "@esbuild/linux-mips64el", - "@esbuild/linux-ppc64", - "@esbuild/linux-riscv64", - "@esbuild/linux-s390x", - "@esbuild/linux-x64", - "@esbuild/netbsd-x64", - "@esbuild/openbsd-x64", - "@esbuild/sunos-x64", - "@esbuild/win32-arm64", - "@esbuild/win32-ia32", - "@esbuild/win32-x64" - ] - }, - "local-pkg@0.5.1": { - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dependencies": [ - "mlly", - "pkg-types" - ] - }, - "mlly@1.7.3": { - "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", - "dependencies": [ - "acorn", - "pathe", - "pkg-types", - "ufo" - ] - }, - "pathe@1.1.2": { - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" - }, - "pkg-types@1.2.1": { - "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", - "dependencies": [ - "confbox", - "mlly", - "pathe" - ] - }, - "resolve.exports@2.0.2": { - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==" - }, - "ufo@1.5.4": { - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==" - }, - "zod-validation-error@3.4.0_zod@3.23.8": { - "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", - "dependencies": [ - "zod" - ] - }, - "zod@3.23.8": { - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==" - } - }, - "workspace": { - "dependencies": [ - "jsr:@bartlomieju/postgres@~0.17.2", - "jsr:@cross/dir@^1.1.0", - "jsr:@hono/hono@^4.6.3", - "jsr:@luca/esbuild-deno-loader@~0.11.1", - "jsr:@std/assert@0.213", - "jsr:@std/async@^1.0.4", - "jsr:@std/cli@^1.0.5", - "jsr:@std/collections@^1.0.5", - "jsr:@std/crypto@^1.0.3", - "jsr:@std/encoding@^1.0.3", - "jsr:@std/fmt@^1.0.1", - "jsr:@std/fs@0.213", - "jsr:@std/jsonc@0.213", - "jsr:@std/path@0.213", - "jsr:@ts-morph/ts-morph@23", - "npm:@asteasolutions/zod-to-openapi@^7.1.1", - "npm:@cloudflare/workers-types@*", - "npm:dedent@^1.5.3", - "npm:esbuild-plugins-node-modules-polyfill@1.6.4", - "npm:esbuild@~0.20.2", - "npm:glob@11", - "npm:nanoevents@9", - "npm:tar@^7.4.3", - "npm:typescript-json-schema@0.62", - "npm:zod-validation-error@^3.3.1", - "npm:zod@^3.23.8" - ] - } -} diff --git a/packages/toolchain/js-utils-embed/js/package.json b/packages/toolchain/js-utils-embed/js/package.json new file mode 100644 index 0000000000..9504e5e9de --- /dev/null +++ b/packages/toolchain/js-utils-embed/js/package.json @@ -0,0 +1,26 @@ +{ + "name": "@rivet-gg/js-utils-embed", + "version": "0.0.0", + "private": true, + "dependencies": { + "@std/cli": "npm:@jsr/std__cli@^1.0.5", + "@std/fmt": "npm:@jsr/std__fmt@^1.0.1", + "@std/fs": "npm:@jsr/std__fs@0.213", + "@std/path": "npm:@jsr/std__path@0.213", + "glob": "^11.0.0", + "unenv": "^1.10.0", + "zod": "^3.24.1", + "zod-validation-error": "^3.3.1" + }, + "type": "module", + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "deno run -A build.ts" + }, + "devDependencies": { + "ts-node": "^10.9.2", + "typescript": "^5.7.3" + } +} diff --git a/packages/toolchain/js-utils-embed/js/src/tasks/build/build.ts b/packages/toolchain/js-utils-embed/js/src/tasks/build/build.ts index 36732edee5..7e65797dd5 100644 --- a/packages/toolchain/js-utils-embed/js/src/tasks/build/build.ts +++ b/packages/toolchain/js-utils-embed/js/src/tasks/build/build.ts @@ -1,56 +1,84 @@ import { resolve } from "@std/path"; import { exists } from "@std/fs"; -import { denoPlugins } from "@luca/esbuild-deno-loader"; import * as esbuild from "esbuild"; +import { nodeless } from "unenv"; import { Input, Output } from "./mod.ts"; +import { createRequire } from "node:module"; +import { nodejsHybridPlugin } from "./nodejs_compat.ts"; + +// const nodelessPlugin: esbuild.Plugin = { +// name: "unenv-nodeless", +// setup(build) { +// const alias = nodeless.alias; +// const re = new RegExp(`^(${Object.keys(alias).join("|")})$`); + +// build.onResolve( +// { filter: re }, +// (args) => { +// const result = alias[args.path]; +// return result ? { path: result } : undefined; +// }, +// ); +// }, +// }; + +//const nodelessPlugin: esbuild.Plugin = { +// name: "unenv-nodeless", +// setup(build) { +// const alias = nodeless.alias; +// const re = new RegExp(`^(${Object.keys(alias).join("|")})$`); // this should be escaped +// +// const require = createRequire(import.meta.url); +// const aliasAbsolute = Object.fromEntries( +// Object.entries(alias).map(([key, value]) => [ +// key, +// require.resolve(value).replace(/\.cjs$/, ".mjs"), +// ]), +// ); +// +// build.onResolve( +// { +// filter: re, +// }, +// (args) => { +// const result = aliasAbsolute[args.path]; +// return result ? { path: result } : undefined; +// }, +// ); +// }, +//}; export async function build(input: Input): Promise { + console.log("Starting build process..."); + + // Install dependencies using yarn + console.log("Installing dependencies..."); + const process = new Deno.Command("yarn", { + args: ["install"], + stdout: "inherit", + stderr: "inherit", + }); + const { success } = await process.output(); + + if (!success) { + throw new Error("Failed to install dependencies"); + } + console.log("Dependencies installed successfully"); + let outfile = resolve(input.outDir, "index.js"); + console.log(`Building to output file: ${outfile}`); + const result = await esbuild.build({ entryPoints: [input.entryPoint], outfile, format: "esm", sourcemap: true, - plugins: [ - // Bundle Deno dependencies - ...denoPlugins({ - loader: "native", - configPath: input.deno.configPath, - importMapURL: input.deno.importMapUrl, - lockPath: input.deno.lockPath, - }), - - // HACK: esbuild-deno-loader does not play nice with - // Windows paths, so we manually resolve any paths that - // start with a Windows path separator (\) and resolve - // them to the full path. - { - name: "fix-windows-paths", - setup(build: esbuild.PluginBuild) { - build.onResolve({ filter: /^\\.*/ }, (args) => { - const resolvedPath = resolve(args.resolveDir, args.path); - if (!exists(resolvedPath, { isFile: true })) { - return { - errors: [{ text: `File could not be resolved: ${resolvedPath}` }], - }; - } - - return { - path: resolve(args.resolveDir, args.path), - }; - }); - }, - } satisfies esbuild.Plugin, - ], - define: { - // HACK: Disable `process.domain` in order to correctly handle this edge case: - // https://github.com/brianc/node-postgres/blob/50c06f9bc6ff2ca1e8d7b7268b9af54ce49d72c1/packages/pg/lib/native/query.js#L126 - "process.domain": "undefined", - }, + platform: "browser", + // Helpful for traces & for RPCs when minified. + keepNames: true, + //plugins: [nodelessPlugin], + plugins: [nodejsHybridPlugin()], external: [ - // Provided by Deno - "node:*", - // Wasm must be loaded as a separate file manually, cannot be bundled "*.wasm", "*.wasm?module", @@ -58,16 +86,28 @@ export async function build(input: Input): Promise { bundle: true, minify: input.bundle.minify, + // Added new configurations + target: ["es2020"], + treeShaking: true, + resolveExtensions: [".js", ".jsx", ".ts", ".tsx", ".json"], + define: { + "process.env.NODE_ENV": '"production"', + }, + // TODO: Remove any logLevel: input.bundle.logLevel as any, metafile: input.bundle.analyzeResult, }); + console.log("Build completed successfully"); let analyzedMetafile = undefined; if (result.metafile) { + console.log("Analyzing metafile..."); analyzedMetafile = await esbuild.analyzeMetafile(result.metafile); + console.log("Metafile analysis complete"); } + console.log("Build process finished"); return { files: ["index.js"], analyzedMetafile, diff --git a/packages/toolchain/js-utils-embed/js/src/tasks/build/mod.ts b/packages/toolchain/js-utils-embed/js/src/tasks/build/mod.ts index ae59a78c4e..6103801710 100644 --- a/packages/toolchain/js-utils-embed/js/src/tasks/build/mod.ts +++ b/packages/toolchain/js-utils-embed/js/src/tasks/build/mod.ts @@ -5,11 +5,6 @@ import { build } from "./build.ts"; export const inputSchema = z.object({ entryPoint: z.string(), outDir: z.string(), - deno: z.object({ - configPath: z.string().optional(), - importMapUrl: z.string().optional(), - lockPath: z.string().optional(), - }), bundle: z.object({ minify: z.boolean(), analyzeResult: z.boolean(), diff --git a/packages/toolchain/js-utils-embed/js/src/tasks/build/nodejs_compat.ts b/packages/toolchain/js-utils-embed/js/src/tasks/build/nodejs_compat.ts new file mode 100644 index 0000000000..d9e9c43cc2 --- /dev/null +++ b/packages/toolchain/js-utils-embed/js/src/tasks/build/nodejs_compat.ts @@ -0,0 +1,250 @@ +import { builtinModules } from "node:module"; +import nodePath from "node:path"; +import { dedent } from "ts-dedent"; +import { cloudflare, env, nodeless } from "unenv"; +import type { Plugin, PluginBuild } from "esbuild"; + +const REQUIRED_NODE_BUILT_IN_NAMESPACE = "node-built-in-modules"; +const REQUIRED_UNENV_ALIAS_NAMESPACE = "required-unenv-alias"; + +/** + * ESBuild plugin to apply the unenv preset. + * + * @param _unenvResolvePaths Root paths used to resolve absolute paths. + * @returns ESBuild plugin + */ +export function nodejsHybridPlugin(_unenvResolvePaths?: string[]): Plugin { + const { alias, inject, external } = env(nodeless, cloudflare); + return { + name: "hybrid-nodejs_compat", + setup(build) { + errorOnServiceWorkerFormat(build); + handleRequireCallsToNodeJSBuiltins(build); + handleUnenvAliasedPackages(build, alias, external); + //handleNodeJSGlobals(build, inject); + }, + }; +} + +const NODEJS_MODULES_RE = new RegExp(`^(node:)?(${builtinModules.join("|")})$`); + +/** + * If we are bundling a "Service Worker" formatted Worker, imports of external modules, + * which won't be inlined/bundled by esbuild, are invalid. + * + * This `onResolve()` handler will error if it identifies node.js external imports. + */ +function errorOnServiceWorkerFormat(build: PluginBuild) { + const paths = new Set(); + build.onStart(() => paths.clear()); + build.onResolve({ filter: NODEJS_MODULES_RE }, (args) => { + paths.add(args.path); + return null; + }); + build.onEnd(() => { + if (build.initialOptions.format === "iife" && paths.size > 0) { + const pathList = new Intl.ListFormat("en-US").format( + Array.from(paths.keys()) + .map((p) => `"${p}"`) + .sort() + ); + return { + errors: [ + { + text: dedent` + Unexpected external import of ${pathList}. + Your worker has no default export, which means it is assumed to be a Service Worker format Worker. + Did you mean to create a ES Module format Worker? + If so, try adding \`export default { ... }\` in your entry-point. + See https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/. + `, + }, + ], + }; + } + }); +} + +/** + * We must convert `require()` calls for Node.js modules to a virtual ES Module that can be imported avoiding the require calls. + * We do this by creating a special virtual ES module that re-exports the library in an onLoad handler. + * The onLoad handler is triggered by matching the "namespace" added to the resolve. + */ +function handleRequireCallsToNodeJSBuiltins(build: PluginBuild) { + build.onResolve({ filter: NODEJS_MODULES_RE }, (args) => { + if (args.kind === "require-call") { + return { + path: args.path, + namespace: REQUIRED_NODE_BUILT_IN_NAMESPACE, + }; + } + }); + build.onLoad( + { filter: /.*/, namespace: REQUIRED_NODE_BUILT_IN_NAMESPACE }, + ({ path }) => { + return { + contents: dedent` + import libDefault from '${path}'; + module.exports = libDefault;`, + loader: "js", + }; + } + ); +} + +/** + * Handles aliased NPM packages. + * + * @param build ESBuild PluginBuild. + * @param alias Aliases resolved to absolute paths. + * @param external external modules. + */ +function handleUnenvAliasedPackages( + build: PluginBuild, + alias: Record, + external: string[] +) { + // esbuild expects alias paths to be absolute + const aliasAbsolute: Record = {}; + for (const [module, unresolvedAlias] of Object.entries(alias)) { + try { + aliasAbsolute[module] = require + .resolve(unresolvedAlias) + .replace(/\.cjs$/, ".mjs"); + } catch (e) { + // this is an alias for package that is not installed in the current app => ignore + } + } + + const UNENV_ALIAS_RE = new RegExp( + `^(${Object.keys(aliasAbsolute).join("|")})$` + ); + + build.onResolve({ filter: UNENV_ALIAS_RE }, (args) => { + const unresolvedAlias = alias[args.path]; + // Convert `require()` calls for NPM packages to a virtual ES Module that can be imported avoiding the require calls. + // Note: Does not apply to Node.js packages that are handled in `handleRequireCallsToNodeJSBuiltins` + if ( + args.kind === "require-call" && + (unresolvedAlias.startsWith("unenv/runtime/npm/") || + unresolvedAlias.startsWith("unenv/runtime/mock/")) + ) { + return { + path: args.path, + namespace: REQUIRED_UNENV_ALIAS_NAMESPACE, + }; + } + + // Resolve the alias to its absolute path and potentially mark it as external + return { + path: aliasAbsolute[args.path], + external: external.includes(unresolvedAlias), + }; + }); + + build.initialOptions.banner = { js: "", ...build.initialOptions.banner }; + build.initialOptions.banner.js += dedent` + function __cf_cjs(esm) { + const cjs = 'default' in esm ? esm.default : {}; + for (const [k, v] of Object.entries(esm)) { + if (k !== 'default') { + Object.defineProperty(cjs, k, { + enumerable: true, + value: v, + }); + } + } + return cjs; + } + `; + + build.onLoad( + { filter: /.*/, namespace: REQUIRED_UNENV_ALIAS_NAMESPACE }, + ({ path }) => { + return { + contents: dedent` + import * as esm from '${path}'; + module.exports = __cf_cjs(esm); + `, + loader: "js", + }; + } + ); +} + +// TODO: +/** + * Inject node globals defined in unenv's `inject` config via virtual modules + */ +//function handleNodeJSGlobals( +// build: PluginBuild, +// inject: Record +//) { +// const UNENV_GLOBALS_RE = /_virtual_unenv_global_polyfill-([^.]+)\.js$/; +// const prefix = nodePath.resolve( +// getBasePath(), +// "_virtual_unenv_global_polyfill-" +// ); +// +// build.initialOptions.inject = [ +// ...(build.initialOptions.inject ?? []), +// //convert unenv's inject keys to absolute specifiers of custom virtual modules that will be provided via a custom onLoad +// ...Object.keys(inject).map( +// (globalName) => `${prefix}${encodeToLowerCase(globalName)}.js` +// ), +// ]; +// +// build.onResolve({ filter: UNENV_GLOBALS_RE }, ({ path }) => ({ path })); +// +// build.onLoad({ filter: UNENV_GLOBALS_RE }, ({ path }) => { +// // eslint-disable-next-line @typescript-eslint/no-non-null-assertion +// const globalName = decodeFromLowerCase(path.match(UNENV_GLOBALS_RE)![1]); +// const { importStatement, exportName } = getGlobalInject(inject[globalName]); +// +// return { +// contents: dedent` +// ${importStatement} +// globalThis.${globalName} = ${exportName}; +// `, +// }; +// }); +//} + +/** + * Get the import statement and export name to be used for the given global inject setting. + */ +function getGlobalInject(globalInject: string | string[]) { + if (typeof globalInject === "string") { + // the mapping is a simple string, indicating a default export, so the string is just the module specifier. + return { + importStatement: `import globalVar from "${globalInject}";`, + exportName: "globalVar", + }; + } + // the mapping is a 2 item tuple, indicating a named export, made up of a module specifier and an export name. + const [moduleSpecifier, exportName] = globalInject; + return { + importStatement: `import { ${exportName} } from "${moduleSpecifier}";`, + exportName, + }; +} + +/** + * Encodes a case sensitive string to lowercase string. + * + * - Escape $ with another $ ("$" -> "$$") + * - Escape uppercase letters with $ and turn them into lowercase letters ("L" -> "$L") + * + * This function exists because ESBuild requires that all resolved paths are case insensitive. + * Without this transformation, ESBuild will clobber /foo/bar.js with /foo/Bar.js + */ +export function encodeToLowerCase(str: string): string { + return str.replace(/[A-Z$]/g, (escape) => `$${escape.toLowerCase()}`); +} + +/** + * Decodes a string lowercased using `encodeToLowerCase` to the original strings + */ +export function decodeFromLowerCase(str: string): string { + return str.replace(/\$[a-z$]/g, (escaped) => escaped[1].toUpperCase()); +} diff --git a/packages/toolchain/js-utils-embed/src/lib.rs b/packages/toolchain/js-utils-embed/src/lib.rs index e3ac49bf06..638e224056 100644 --- a/packages/toolchain/js-utils-embed/src/lib.rs +++ b/packages/toolchain/js-utils-embed/src/lib.rs @@ -3,53 +3,21 @@ use include_dir::{include_dir, Dir}; use std::path::PathBuf; use tokio::fs; -const JS_UTILS_DIR: Dir = include_dir!("$JS_UTILS_PATH"); -const JS_UTILS_HASH: &'static str = env!("JS_UTILS_HASH"); +const JS_UTILS_DIST_PATH: Dir = include_dir!("$JS_UTILS_DIST_PATH"); +const JS_UTILS_DIST_HASH: &'static str = env!("JS_UTILS_DIST_HASH"); /// Return a path for the source dir. If one does not exist, the source dir will automatically be /// extracted and executables will be set. -pub async fn src_path(data_dir: &PathBuf) -> Result { +pub async fn dist_path(data_dir: &PathBuf) -> Result { // Create path to src based on hash - let src_dir = data_dir.join("js-utils").join(JS_UTILS_HASH); + let src_dir = data_dir.join("js-utils").join(JS_UTILS_DIST_HASH); // Write js-utils if does not exist if !src_dir.exists() { fs::create_dir_all(&src_dir).await?; - tokio::task::block_in_place(|| JS_UTILS_DIR.extract(&src_dir))?; - - // Update executables - #[cfg(unix)] - set_executables(&JS_UTILS_DIR, &src_dir).await?; + tokio::task::block_in_place(|| JS_UTILS_DIST_PATH.extract(&src_dir))?; } Ok(src_dir) } -/// HACK: Make all binaries in `bin` folders executable. This is because -/// bundling the vendored folders strips permissions, so executables can't be ran. -#[cfg(unix)] -async fn set_executables(dir: &Dir<'_>, fs_path: &PathBuf) -> Result<()> { - use include_dir::DirEntry; - use std::os::unix::fs::PermissionsExt; - - for entry in dir.entries() { - match entry { - DirEntry::Dir(subdir) => { - let file_name = subdir.path().file_name().unwrap_or_default(); - if file_name == "bin" || file_name == ".bin" { - for file_entry in subdir.files() { - let file_path = fs_path.join(file_entry.path()); - let metadata = fs::metadata(&file_path).await?; - let mut perms = metadata.permissions(); - perms.set_mode(perms.mode() | 0o111); - fs::set_permissions(file_path, perms).await?; - } - } - - Box::pin(set_executables(subdir, &fs_path)).await?; - } - DirEntry::File(_) => {} // Skip files at this level - } - } - Ok(()) -} diff --git a/packages/toolchain/toolchain/src/config/build/javascript.rs b/packages/toolchain/toolchain/src/config/build/javascript.rs index aa5bb7d544..fad7834802 100644 --- a/packages/toolchain/toolchain/src/config/build/javascript.rs +++ b/packages/toolchain/toolchain/src/config/build/javascript.rs @@ -8,34 +8,10 @@ use super::Compression; #[serde(rename_all = "snake_case")] pub struct Build { pub script: String, - pub bundler: Option, - #[serde(default)] - pub deno: Deno, #[serde(default)] pub unstable: Unstable, } -impl Build { - pub fn bundler(&self) -> Bundler { - self.bundler.unwrap_or(Bundler::Deno) - } -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Bundler { - Deno, - None, -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case", deny_unknown_fields)] -pub struct Deno { - pub config_path: Option, - pub import_map_url: Option, - pub lock_path: Option, -} - #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case", deny_unknown_fields)] pub struct Unstable { @@ -44,6 +20,7 @@ pub struct Unstable { pub esbuild_log_level: Option, pub compression: Option, pub dump_build: Option, + pub no_bundler: Option, } impl Unstable { @@ -69,4 +46,8 @@ impl Unstable { pub fn dump_build(&self) -> bool { self.dump_build.unwrap_or(false) } + + pub fn no_bundler(&self) -> bool { + self.no_bundler.unwrap_or(false) + } } diff --git a/packages/toolchain/toolchain/src/tasks/deploy/js.rs b/packages/toolchain/toolchain/src/tasks/deploy/js.rs index 478e6577f6..8c0273ec42 100644 --- a/packages/toolchain/toolchain/src/tasks/deploy/js.rs +++ b/packages/toolchain/toolchain/src/tasks/deploy/js.rs @@ -43,106 +43,68 @@ pub async fn build_and_upload( } // Bundle JS - match opts.build_config.bundler() { - config::build::javascript::Bundler::Deno => { - // Validate that the script path has a .ts or .js extension - let script_path = project_root.join(&opts.build_config.script); - let ext = script_path.extension().and_then(|s| s.to_str()); - ensure!( - ext == Some("ts") || ext == Some("tsx") || ext == Some("js") || ext == Some("jsx"), - "script file must have a .ts or .js extension for Deno bundler" - ); - - // Search for deno.json or deno.jsonc - let config_path = if let Some(config_path) = opts.build_config.deno.config_path.clone() - { - Some(config_path) - } else { - ["deno.json", "deno.jsonc"].iter().find_map(|file_name| { - let path = project_root.join(file_name); - if path.exists() { - Some(path.display().to_string()) - } else { - None - } - }) - }; - - // Search for a Deno lockfile - let lock_path = if let Some(lock_path) = opts.build_config.deno.lock_path.clone() { - Some(lock_path) - } else { - let project_deno_lockfile_path = project_root.join("deno.lock"); - if project_deno_lockfile_path.exists() { - Some(project_deno_lockfile_path.display().to_string()) - } else { - None - } - }; - - // Define paths - let import_map_url = opts.build_config.deno.import_map_url.clone(); - - // Check the project before deploying - deno_check_script(CheckOpts { - script_path: &script_path, - config_path: config_path.as_deref(), - import_map_url: import_map_url.as_deref(), - lock_path: lock_path.as_deref(), - }) - .await?; - - // Build the bundle to the output dir. This will bundle all Deno dependencies into a - // single JS file. - // - // The Deno command is run in the project root, so `config_path`, `lock_path`, etc can - // all safely be passed as relative paths without joining with `project_root`. - let output = js_utils::run_command_and_parse_output::< - js_utils::schemas::build::Input, - js_utils::schemas::build::Output, - >( - "src/tasks/build/mod.ts", - &js_utils::schemas::build::Input { - entry_point: script_path, - out_dir: build_dir.path().to_path_buf(), - deno: js_utils::schemas::build::Deno { - config_path, - import_map_url, - lock_path, - }, - bundle: js_utils::schemas::build::Bundle { - minify: opts.build_config.unstable.minify(), - analyze_result: opts.build_config.unstable.analyze_result(), - log_level: opts.build_config.unstable.esbuild_log_level(), - }, + if !opts.build_config.unstable.no_bundler() { + // Validate that the script path has a .ts or .js extension + let script_path = project_root.join(&opts.build_config.script); + let ext = script_path.extension().and_then(|s| s.to_str()); + ensure!( + ext == Some("ts") || ext == Some("tsx") || ext == Some("js") || ext == Some("jsx"), + "script file must have a .ts or .js extension for Deno bundler" + ); + + // Check the project before deploying + //deno_check_script(CheckOpts { + // script_path: &script_path, + // config_path: config_path.as_deref(), + // import_map_url: import_map_url.as_deref(), + // lock_path: lock_path.as_deref(), + //}) + //.await?; + + // Build the bundle to the output dir. This will bundle all Deno dependencies into a + // single JS file. + // + // The Deno command is run in the project root, so `config_path`, `lock_path`, etc can + // all safely be passed as relative paths without joining with `project_root`. + let output = js_utils::run_command_and_parse_output::< + js_utils::schemas::build::Input, + js_utils::schemas::build::Output, + >( + "src/tasks/build/mod.js", + &js_utils::schemas::build::Input { + entry_point: script_path, + out_dir: build_dir.path().to_path_buf(), + bundle: js_utils::schemas::build::Bundle { + minify: opts.build_config.unstable.minify(), + analyze_result: opts.build_config.unstable.analyze_result(), + log_level: opts.build_config.unstable.esbuild_log_level(), }, - ) - .await?; - if let Some(analyze_result) = output.analyzed_metafile { - task.log("[Bundle Analysis]"); - task.log(analyze_result); - } + }, + ) + .await?; + if let Some(analyze_result) = output.analyzed_metafile { + task.log("[Bundle Analysis]"); + task.log(analyze_result); } - config::build::javascript::Bundler::None => { - // Ensure the script path has a .js extension - let script_path = project_root.join(opts.build_config.script); - ensure!( - script_path.extension().and_then(|s| s.to_str()) == Some("js"), - "script file must have a .js extension when not using a bundler" - ); - - // Validate script exists - match fs::metadata(&script_path).await { - Result::Ok(_) => {} - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - bail!("script not found: {}", script_path.display()) - } - Err(err) => bail!("failed to read script at {}: {err}", script_path.display()), + } else { + // Ensure the script path has a .js extension + let script_path = project_root.join(opts.build_config.script); + ensure!( + script_path.extension().and_then(|s| s.to_str()) == Some("js"), + "script file must have a .js extension when not using a bundler" + ); + + // Validate script exists + match fs::metadata(&script_path).await { + Result::Ok(_) => {} + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + bail!("script not found: {}", script_path.display()) } - - // Copy index file to build dir - fs::copy(&script_path, build_dir.path().join(BUILD_INDEX_NAME)).await?; + Err(err) => bail!("failed to read script at {}: {err}", script_path.display()), } + + // Copy index file to build dir + fs::copy(&script_path, build_dir.path().join(BUILD_INDEX_NAME)).await?; }; // Deploy JS build diff --git a/packages/toolchain/toolchain/src/tasks/deploy/manager.rs b/packages/toolchain/toolchain/src/tasks/deploy/manager.rs index 36a7f216e7..d29378ffa5 100644 --- a/packages/toolchain/toolchain/src/tasks/deploy/manager.rs +++ b/packages/toolchain/toolchain/src/tasks/deploy/manager.rs @@ -20,15 +20,16 @@ pub async fn deploy( task: task::TaskCtx, opts: DeployOpts, ) -> Result { - // Get source - let manager_src_path = rivet_actors_sdk_embed::src_path(&paths::data_dir()?).await?; - let tags = HashMap::from([ ("name".to_string(), "manager".to_string()), ("owner".to_string(), "rivet".to_string()), ]); // Deploy manager + let manager_script_path = rivet_actors_sdk_embed::dist_path(&paths::data_dir()?) + .await? + .join("manager") + .join("index.js"); let build_id = super::js::build_and_upload( ctx, task.clone(), @@ -36,19 +37,11 @@ pub async fn deploy( env: opts.env.clone(), tags: tags.clone(), build_config: config::build::javascript::Build { - script: manager_src_path - .join("manager") - .join("src") - .join("mod.ts") - .display() - .to_string(), - bundler: Some(config::build::javascript::Bundler::Deno), - deno: config::build::javascript::Deno { - config_path: None, - import_map_url: None, - lock_path: Some(manager_src_path.join("deno.lock").display().to_string()), + script: manager_script_path.display().to_string(), + unstable: config::build::javascript::Unstable { + no_bundler: Some(true), + ..opts.manager_config.js_unstable.clone() }, - unstable: opts.manager_config.js_unstable.clone(), }, }, ) diff --git a/packages/toolchain/toolchain/src/util/js_utils/mod.rs b/packages/toolchain/toolchain/src/util/js_utils/mod.rs index 2ba4fa3a75..ca19a594b9 100644 --- a/packages/toolchain/toolchain/src/util/js_utils/mod.rs +++ b/packages/toolchain/toolchain/src/util/js_utils/mod.rs @@ -19,7 +19,7 @@ async fn base_url() -> Result { let base_url = if let Some(url) = std::env::var("_RIVET_JS_UTILS_SRC_DIR").ok() { url } else { - rivet_js_utils_embed::src_path(&paths::data_dir()?) + rivet_js_utils_embed::dist_path(&paths::data_dir()?) .await? .display() .to_string() @@ -54,16 +54,14 @@ pub async fn build_backend_command_raw(opts: CommandOpts) -> Result "run".into(), "--quiet".into(), "--no-check".into(), - "--allow-net".into(), - "--allow-read".into(), - "--allow-env".into(), - "--allow-run".into(), - "--allow-write".into(), - "--allow-sys".into(), - "--config".into(), - format!("{base_url}/deno.jsonc"), - "--lock".into(), - format!("{base_url}/deno.lock"), + //"--allow-net".into(), + //"--allow-read".into(), + //"--allow-env".into(), + //"--allow-run".into(), + //"--allow-write".into(), + //"--allow-sys".into(), + "--allow-all".into(), + "--unstable-bare-node-builtins".into(), format!("{base_url}/{}", opts.task_path), "--input".into(), input_json, diff --git a/packages/toolchain/toolchain/src/util/js_utils/schemas.rs b/packages/toolchain/toolchain/src/util/js_utils/schemas.rs index 2c4bd14129..2f9499dc1d 100644 --- a/packages/toolchain/toolchain/src/util/js_utils/schemas.rs +++ b/packages/toolchain/toolchain/src/util/js_utils/schemas.rs @@ -7,21 +7,9 @@ pub mod build { pub struct Input { pub entry_point: PathBuf, pub out_dir: PathBuf, - pub deno: Deno, pub bundle: Bundle, } - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - pub struct Deno { - #[serde(skip_serializing_if = "Option::is_none")] - pub config_path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub import_map_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub lock_path: Option, - } - #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct Bundle { diff --git a/scripts/js_utils/build.ts b/scripts/js_utils/build.ts index e061eeca42..8d064ff28b 100755 --- a/scripts/js_utils/build.ts +++ b/scripts/js_utils/build.ts @@ -26,28 +26,14 @@ const JS_UTILS_DIR = resolve(ROOT_DIR, "packages/toolchain/js-utils-embed/js"); const input = { entryPoint: resolve(Deno.cwd(), entry), - outDir: resolve(Deno.cwd(), "out"), - deno: { - //configPath: resolve(Deno.cwd(), "deno.jsonc"), - //importMapUrl: z.string().optional(), - lockPath: resolve(Deno.cwd(), "deno.lock"), - }, + outDir: resolve(Deno.cwd(), "dist"), bundle: { minify: false, - analyzeResult: true, + analyzeResult: false, logLevel: "debug", }, }; -const output1 = await new Deno.Command("deno", { - args: [ - "clean" - ], - cwd: JS_UTILS_DIR, - stdout: "inherit", - stderr: "inherit", -}).output(); - const output = await new Deno.Command("deno", { args: [ "run", diff --git a/sdks/actor/manager/package.json b/sdks/actor/manager/package.json index 296539c3a2..356f47400f 100644 --- a/sdks/actor/manager/package.json +++ b/sdks/actor/manager/package.json @@ -5,6 +5,7 @@ "dependencies": { "@rivet-gg/actor-common": "workspace:*", "@rivet-gg/manager-protocol": "workspace:*", - "hono": "^4.6.17" + "hono": "^4.6.17", + "ts-dedent": "^2.2.0" } } diff --git a/sdks/actor/manager/src/mod.ts b/sdks/actor/manager/src/mod.ts index d32df20a36..5be1e381aa 100644 --- a/sdks/actor/manager/src/mod.ts +++ b/sdks/actor/manager/src/mod.ts @@ -1,124 +1,147 @@ -//export default class Manager { -// private readonly endpoint: string; -// private readonly rivetClient: RivetClient; -// private readonly environment: RivetEnvironment; -// -// constructor(private readonly ctx: ActorContext) { -// const endpoint = Deno.env.get("RIVET_API_ENDPOINT"); -// assertExists(endpoint, "missing RIVET_API_ENDPOINT"); -// const token = Deno.env.get("RIVET_SERVICE_TOKEN"); -// assertExists(token, "missing RIVET_SERVICE_TOKEN"); -// -// this.endpoint = endpoint; -// -// this.rivetClient = new RivetClient({ -// environment: endpoint, -// token, -// }); -// -// this.environment = { -// project: this.ctx.metadata.project.slug, -// environment: this.ctx.metadata.environment.slug, -// }; -// } -// -// static async start(ctx: ActorContext) { -// setupLogging(); -// -// // biome-ignore lint/complexity/noThisInStatic: Must be used for default actor entrypoint -// const manager = new this(ctx); -// await manager.#run(); -// } -// -// async #run() { -// const portStr = Deno.env.get("PORT_HTTP"); -// if (!portStr) throw "Missing port"; -// const port = Number.parseInt(portStr); -// if (!Number.isFinite(port)) throw "Invalid port"; -// -// const app = new Hono(); -// -// app.use( -// "/*", -// cors({ -// origin: (origin) => { -// return origin.includes("localhost"); -// }, -// }), -// ); -// -// app.get("/rivet/config", (c: HonoContext) => { -// return c.json({ -// // HACK(RVT-4376): Replace DNS address used for local dev envs with public address -// endpoint: this.endpoint.replace("rivet-server", "127.0.0.1"), -// project: this.environment.project, -// environment: this.environment.environment, -// } satisfies RivetConfigResponse); -// }); -// -// app.post("/actors", async (c: HonoContext) => { -// // Get actor -// const body = ActorsRequestSchema.parse(await c.req.json()); -// const actor = await queryActor( -// this.rivetClient, -// this.environment, -// body.query, -// ); -// -// // Fetch port -// const httpPort = actor.network.ports[PORT_NAME]; -// assertExists(httpPort, "missing port"); -// const hostname = httpPort.hostname; -// assertExists(hostname); -// const port = httpPort.port; -// assertExists(port); -// -// let isTls = false; -// switch (httpPort.protocol) { -// case "https": -// isTls = true; -// break; -// case "http": -// case "tcp": -// isTls = false; -// break; -// case "tcp_tls": -// case "udp": -// throw new Error(`Invalid protocol ${httpPort.protocol}`); -// default: -// assertUnreachable(httpPort.protocol); -// } -// -// const path = httpPort.path ?? ""; -// -// const endpoint = `${ -// isTls ? "https" : "http" -// }://${hostname}:${port}${path}`; -// -// return c.json({ endpoint } satisfies ActorsResponse); -// }); -// -// app.all("*", (c) => { -// return c.text("Not Found", 404); -// }); -// -// logger().info("server running", { port }); -// const server = Deno.serve( -// { -// port, -// hostname: "0.0.0.0", -// // Remove "Listening on ..." message -// onListen() {}, -// }, -// app.fetch, -// ); -// -// logger().debug("rivet endpoint", { -// endpoint: this.endpoint, -// project: this.ctx.metadata.project.slug, -// environment: this.ctx.metadata.environment.slug, -// }); -// -// await server.finished; -// } -//} +// @ts-types="../../common/dist/log.d.ts" +import { setupLogging } from "@rivet-gg/actor-common/log"; +// @ts-types="../../common/dist/network.d.ts" +import { PORT_NAME } from "@rivet-gg/actor-common/network"; +// @ts-types="../../common/dist/utils.d.ts" +import { + type RivetEnvironment, + assertUnreachable, +} from "@rivet-gg/actor-common/utils"; +import type { ActorContext } from "@rivet-gg/actor-core"; +import { RivetClient } from "@rivet-gg/api"; +// @ts-types="../../manager-protocol/dist/mod.d.ts" +import { + ActorsRequestSchema, + type ActorsResponse, + type RivetConfigResponse, +} from "@rivet-gg/manager-protocol"; +import { assertExists } from "@std/assert/exists"; +import { Hono, type Context as HonoContext } from "hono"; +import { cors } from "hono/cors"; +import { logger } from "./log.ts"; +import { queryActor } from "./query_exec.ts"; + +export default class Manager { + private readonly endpoint: string; + private readonly rivetClient: RivetClient; + private readonly environment: RivetEnvironment; + + constructor(private readonly ctx: ActorContext) { + const endpoint = Deno.env.get("RIVET_API_ENDPOINT"); + assertExists(endpoint, "missing RIVET_API_ENDPOINT"); + const token = Deno.env.get("RIVET_SERVICE_TOKEN"); + assertExists(token, "missing RIVET_SERVICE_TOKEN"); + + this.endpoint = endpoint; + + this.rivetClient = new RivetClient({ + environment: endpoint, + token, + }); + + this.environment = { + project: this.ctx.metadata.project.slug, + environment: this.ctx.metadata.environment.slug, + }; + } + + static async start(ctx: ActorContext) { + setupLogging(); + + // biome-ignore lint/complexity/noThisInStatic: Must be used for default actor entrypoint + const manager = new this(ctx); + await manager.#run(); + } + + async #run() { + const portStr = Deno.env.get("PORT_HTTP"); + if (!portStr) throw "Missing port"; + const port = Number.parseInt(portStr); + if (!Number.isFinite(port)) throw "Invalid port"; + + const app = new Hono(); + + app.use( + "/*", + cors({ + origin: (origin) => { + return origin.includes("localhost") ? origin : null; + }, + }), + ); + + app.get("/rivet/config", (c: HonoContext) => { + return c.json({ + // HACK(RVT-4376): Replace DNS address used for local dev envs with public address + endpoint: this.endpoint.replace("rivet-server", "127.0.0.1"), + project: this.environment.project, + environment: this.environment.environment, + } satisfies RivetConfigResponse); + }); + + app.post("/actors", async (c: HonoContext) => { + // Get actor + const body = ActorsRequestSchema.parse(await c.req.json()); + const actor = await queryActor( + this.rivetClient, + this.environment, + body.query, + ); + + // Fetch port + const httpPort = actor.network.ports[PORT_NAME]; + assertExists(httpPort, "missing port"); + const hostname = httpPort.hostname; + assertExists(hostname); + const port = httpPort.port; + assertExists(port); + + let isTls = false; + switch (httpPort.protocol) { + case "https": + isTls = true; + break; + case "http": + case "tcp": + isTls = false; + break; + case "tcp_tls": + case "udp": + throw new Error(`Invalid protocol ${httpPort.protocol}`); + default: + assertUnreachable(httpPort.protocol); + } + + const path = httpPort.path ?? ""; + + const endpoint = `${ + isTls ? "https" : "http" + }://${hostname}:${port}${path}`; + + return c.json({ endpoint } satisfies ActorsResponse); + }); + + app.all("*", (c) => { + return c.text("Not Found", 404); + }); + + logger().info("server running", { port }); + const server = Deno.serve( + { + port, + hostname: "0.0.0.0", + // Remove "Listening on ..." message + onListen() {}, + }, + app.fetch, + ); + + logger().debug("rivet endpoint", { + endpoint: this.endpoint, + project: this.ctx.metadata.project.slug, + environment: this.ctx.metadata.environment.slug, + }); + + await server.finished; + } +} diff --git a/site/src/content/docs/toolchain-spec.json b/site/src/content/docs/toolchain-spec.json index 5a22ace0f6..992cf4e9cb 100644 --- a/site/src/content/docs/toolchain-spec.json +++ b/site/src/content/docs/toolchain-spec.json @@ -86,21 +86,6 @@ "script" ], "properties": { - "bundler": { - "$ref": "#/definitions/Bundler" - }, - "deno": { - "default": { - "config_path": null, - "import_map_url": null, - "lock_path": null - }, - "allOf": [ - { - "$ref": "#/definitions/Deno" - } - ] - }, "script": { "type": "string" }, @@ -110,7 +95,8 @@ "analyze_result": null, "esbuild_log_level": null, "compression": null, - "dump_build": null + "dump_build": null, + "no_bundler": null }, "allOf": [ { @@ -163,13 +149,6 @@ } ] }, - "Bundler": { - "type": "string", - "enum": [ - "deno", - "none" - ] - }, "Compression": { "oneOf": [ { @@ -188,21 +167,6 @@ } ] }, - "Deno": { - "type": "object", - "properties": { - "config_path": { - "type": "string" - }, - "import_map_url": { - "type": "string" - }, - "lock_path": { - "type": "string" - } - }, - "additionalProperties": false - }, "ManagerUnstable": { "type": "object", "properties": { @@ -223,6 +187,9 @@ }, "minify": { "type": "boolean" + }, + "no_bundler": { + "type": "boolean" } }, "additionalProperties": false @@ -262,6 +229,9 @@ }, "minify": { "type": "boolean" + }, + "no_bundler": { + "type": "boolean" } }, "additionalProperties": false @@ -276,7 +246,8 @@ "analyze_result": null, "esbuild_log_level": null, "compression": null, - "dump_build": null + "dump_build": null, + "no_bundler": null }, "allOf": [ { diff --git a/yarn.lock b/yarn.lock index 78ee303945..095348bf34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,80 @@ __metadata: version: 8 cacheKey: 10c0 +"@ai-sdk/openai@npm:^1.1.0": + version: 1.1.0 + resolution: "@ai-sdk/openai@npm:1.1.0" + dependencies: + "@ai-sdk/provider": "npm:1.0.4" + "@ai-sdk/provider-utils": "npm:2.1.0" + peerDependencies: + zod: ^3.0.0 + checksum: 10c0/70115874a1e34f8086e58ba1a2fefe77f82dc536a846ec827194a5f7f26918f62cd2613c364717dc77e02bd4506ede1dd832fa7da46a03b42524e03decd8f1cd + languageName: node + linkType: hard + +"@ai-sdk/provider-utils@npm:2.1.0": + version: 2.1.0 + resolution: "@ai-sdk/provider-utils@npm:2.1.0" + dependencies: + "@ai-sdk/provider": "npm:1.0.4" + eventsource-parser: "npm:^3.0.0" + nanoid: "npm:^3.3.8" + secure-json-parse: "npm:^2.7.0" + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + checksum: 10c0/400a489d3d13a7039b57ae77511abdc2f238af369c51a7019960bab1595765141a1adb5a567a3505a41651d77e632b2521667c7022fe51102a862c282b8ae416 + languageName: node + linkType: hard + +"@ai-sdk/provider@npm:1.0.4": + version: 1.0.4 + resolution: "@ai-sdk/provider@npm:1.0.4" + dependencies: + json-schema: "npm:^0.4.0" + checksum: 10c0/6d79b6c29922a7bbba6d38fadee098e6e41e97b985007ccc0f06f924243b242bd9817e6ef6ec2a71cf796da1e0167c56bac3805425d4c8717a5cbf061499bd1f + languageName: node + linkType: hard + +"@ai-sdk/react@npm:1.1.0": + version: 1.1.0 + resolution: "@ai-sdk/react@npm:1.1.0" + dependencies: + "@ai-sdk/provider-utils": "npm:2.1.0" + "@ai-sdk/ui-utils": "npm:1.1.0" + swr: "npm:^2.2.5" + throttleit: "npm:2.1.0" + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + checksum: 10c0/e714a7a90511ef38c11c1eb36f8b6b7d4fc2a037172f2db98d49c96af511ec62649aa9e6bcbd5363ce4f5dea8bdd8ed0a2e021176718b214a8189f61a465550f + languageName: node + linkType: hard + +"@ai-sdk/ui-utils@npm:1.1.0": + version: 1.1.0 + resolution: "@ai-sdk/ui-utils@npm:1.1.0" + dependencies: + "@ai-sdk/provider": "npm:1.0.4" + "@ai-sdk/provider-utils": "npm:2.1.0" + zod-to-json-schema: "npm:^3.24.1" + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + checksum: 10c0/c47d5b358a12671a5f0303016cd55513f9b69660b316060293440566f487010dfba5e1c1891c87a26756362efed0815da2b26235a845ad58840b815c7dc73b7b + languageName: node + linkType: hard + "@alloc/quick-lru@npm:^5.2.0": version: 5.2.0 resolution: "@alloc/quick-lru@npm:5.2.0" @@ -350,6 +424,27 @@ __metadata: languageName: node linkType: hard +"@clack/core@npm:0.4.1": + version: 0.4.1 + resolution: "@clack/core@npm:0.4.1" + dependencies: + picocolors: "npm:^1.0.0" + sisteransi: "npm:^1.0.5" + checksum: 10c0/60c59e2d0017ce81567566c4f2a288f1738ef6356e25d9c56055be68aa449f75b6a7271b32c335213eb3a7af4b02db90de88c6999c432f09ca00c3d8004ea95b + languageName: node + linkType: hard + +"@clack/prompts@npm:^0.9.1": + version: 0.9.1 + resolution: "@clack/prompts@npm:0.9.1" + dependencies: + "@clack/core": "npm:0.4.1" + picocolors: "npm:^1.0.0" + sisteransi: "npm:^1.0.5" + checksum: 10c0/6cda9f56963dcbbfca4d9a64c82cf57e7f00dd563cd9e9ad28973b10ac761723fc21453254effbf08d5862efd57bad41d48008316c345202b74035ae905329cf + languageName: node + linkType: hard + "@codemirror/autocomplete@npm:^6.0.0": version: 6.18.4 resolution: "@codemirror/autocomplete@npm:6.18.4" @@ -481,6 +576,15 @@ __metadata: languageName: node linkType: hard +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 + languageName: node + linkType: hard + "@date-fns/tz@npm:^1.2.0": version: 1.2.0 resolution: "@date-fns/tz@npm:1.2.0" @@ -1547,7 +1651,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e @@ -1568,6 +1672,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -1585,6 +1699,15 @@ __metadata: languageName: node linkType: hard +"@jsr/std__assert@npm:^0.213.1": + version: 0.213.1 + resolution: "@jsr/std__assert@npm:0.213.1::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__assert%2F0.213.1.tgz" + dependencies: + "@jsr/std__fmt": "npm:^0.213.1" + checksum: 10c0/06d5c9c34e4aa083149567970769d319f18e7565e9e4d4a300bbda18ede999cc121a676d936aee6f7c78968ee304ef39e60a0e65e810902cbd8d178f4a3fc1cd + languageName: node + linkType: hard + "@jsr/std__bytes@npm:^1.0.2, @jsr/std__bytes@npm:^1.0.3, @jsr/std__bytes@npm:^1.0.4": version: 1.0.4 resolution: "@jsr/std__bytes@npm:1.0.4::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__bytes%2F1.0.4.tgz" @@ -1592,7 +1715,14 @@ __metadata: languageName: node linkType: hard -"@jsr/std__fmt@npm:^1.0.4": +"@jsr/std__fmt@npm:^0.213.1": + version: 0.213.1 + resolution: "@jsr/std__fmt@npm:0.213.1::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__fmt%2F0.213.1.tgz" + checksum: 10c0/cb44bdad7e5da09e50c180f24e047058014445aa3bd3bfcc2bcf17883fb28e9fcee6c8d1c2665cd0cac12d87f2a18ec8865298579a8e79ea3b2580b34d023869 + languageName: node + linkType: hard + +"@jsr/std__fmt@npm:^1.0.4, @std/fmt@npm:@jsr/std__fmt@^1.0.1": version: 1.0.4 resolution: "@jsr/std__fmt@npm:1.0.4::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__fmt%2F1.0.4.tgz" checksum: 10c0/bf16f6add4892d4e287fda6d3c5822b03a7309423a886d0451c731aa21d8cb8b02fe356ee4b554e968b04d508c686a463dcff9bc6cf292482f96e93c14f5bc92 @@ -1624,6 +1754,15 @@ __metadata: languageName: node linkType: hard +"@jsr/std__path@npm:^0.213.1, @std/path@npm:@jsr/std__path@0.213": + version: 0.213.1 + resolution: "@jsr/std__path@npm:0.213.1::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__path%2F0.213.1.tgz" + dependencies: + "@jsr/std__assert": "npm:^0.213.1" + checksum: 10c0/e4d142ee68f3263ae5c8d3244939e49f6f7819965945de853eb2a0d98aff825b64b0585e38fcb8b8950a9ddc5611849c416ddea4c9fd23c04b10f108ec72756a + languageName: node + linkType: hard + "@jsr/std__path@npm:^1.0.8": version: 1.0.8 resolution: "@jsr/std__path@npm:1.0.8::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__path%2F1.0.8.tgz" @@ -1965,6 +2104,13 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api@npm:1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2908,7 +3054,7 @@ __metadata: languageName: unknown linkType: soft -"@rivet-gg/actor-client@workspace:sdks/actor/client": +"@rivet-gg/actor-client@workspace:*, @rivet-gg/actor-client@workspace:sdks/actor/client": version: 0.0.0-use.local resolution: "@rivet-gg/actor-client@workspace:sdks/actor/client" dependencies: @@ -2957,6 +3103,7 @@ __metadata: "@rivet-gg/actor-common": "workspace:*" "@rivet-gg/manager-protocol": "workspace:*" hono: "npm:^4.6.17" + ts-dedent: "npm:^2.2.0" languageName: unknown linkType: soft @@ -2970,7 +3117,7 @@ __metadata: languageName: unknown linkType: soft -"@rivet-gg/actor@workspace:sdks/actor/runtime": +"@rivet-gg/actor@workspace:*, @rivet-gg/actor@workspace:sdks/actor/runtime": version: 0.0.0-use.local resolution: "@rivet-gg/actor@workspace:sdks/actor/runtime" dependencies: @@ -3185,6 +3332,23 @@ __metadata: languageName: unknown linkType: soft +"@rivet-gg/js-utils-embed@workspace:packages/toolchain/js-utils-embed/js": + version: 0.0.0-use.local + resolution: "@rivet-gg/js-utils-embed@workspace:packages/toolchain/js-utils-embed/js" + dependencies: + "@std/cli": "npm:@jsr/std__cli@^1.0.5" + "@std/fmt": "npm:@jsr/std__fmt@^1.0.1" + "@std/fs": "npm:@jsr/std__fs@0.213" + "@std/path": "npm:@jsr/std__path@0.213" + glob: "npm:^11.0.0" + ts-node: "npm:^10.9.2" + typescript: "npm:^5.7.3" + unenv: "npm:^1.10.0" + zod: "npm:^3.24.1" + zod-validation-error: "npm:^3.3.1" + languageName: unknown + linkType: soft + "@rivet-gg/manager-protocol@workspace:*, @rivet-gg/manager-protocol@workspace:sdks/actor/manager-protocol": version: 0.0.0-use.local resolution: "@rivet-gg/manager-protocol@workspace:sdks/actor/manager-protocol" @@ -3750,6 +3914,23 @@ __metadata: languageName: node linkType: hard +"@std/cli@npm:@jsr/std__cli@^1.0.5": + version: 1.0.10 + resolution: "@jsr/std__cli@npm:1.0.10::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__cli%2F1.0.10.tgz" + checksum: 10c0/385aa1abeacdda66eae29e927fb136b291f2b93368ad23ce6265dfc1b4e89d07b481b69f3d3b71b4139924ee64290ff359b4f9e0f75bafaf90b1cf5a6e175015 + languageName: node + linkType: hard + +"@std/fs@npm:@jsr/std__fs@0.213": + version: 0.213.1 + resolution: "@jsr/std__fs@npm:0.213.1::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__fs%2F0.213.1.tgz" + dependencies: + "@jsr/std__assert": "npm:^0.213.1" + "@jsr/std__path": "npm:^0.213.1" + checksum: 10c0/04793bf2ead2838c8a95c33761c6b5873f81cc08273789e0e8361e770a1e6df6f785c9d4c89f5721c9df4675d797677d4d1e294c761478c2a336cff77191a1b6 + languageName: node + linkType: hard + "@std/log@npm:@jsr/std__log@^0.224.12": version: 0.224.13 resolution: "@jsr/std__log@npm:0.224.13::__archiveUrl=https%3A%2F%2Fnpm.jsr.io%2F~%2F11%2F%40jsr%2Fstd__log%2F0.224.13.tgz" @@ -4048,6 +4229,34 @@ __metadata: languageName: node linkType: hard +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node10@npm:1.0.11" + checksum: 10c0/28a0710e5d039e0de484bdf85fee883bfd3f6a8980601f4d44066b0a6bcd821d31c4e231d1117731c4e24268bd4cf2a788a6787c12fc7f8d11014c07d582783c + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb + languageName: node + linkType: hard + "@types/acorn@npm:^4.0.0": version: 4.0.6 resolution: "@types/acorn@npm:4.0.6" @@ -4222,6 +4431,13 @@ __metadata: languageName: node linkType: hard +"@types/diff-match-patch@npm:^1.0.36": + version: 1.0.36 + resolution: "@types/diff-match-patch@npm:1.0.36" + checksum: 10c0/0bad011ab138baa8bde94e7815064bb881f010452463272644ddbbb0590659cb93f7aa2776ff442c6721d70f202839e1053f8aa62d801cc4166f7a3ea9130055 + languageName: node + linkType: hard + "@types/diff@npm:^6.0.0": version: 6.0.0 resolution: "@types/diff@npm:6.0.0" @@ -4722,7 +4938,16 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.8.1, acorn@npm:^8.9.0": +"acorn-walk@npm:^8.1.1": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 + languageName: node + linkType: hard + +"acorn@npm:^8.0.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.9.0": version: 8.14.0 resolution: "acorn@npm:8.14.0" bin: @@ -4747,6 +4972,42 @@ __metadata: languageName: node linkType: hard +"ai-agent-shopper@workspace:examples/javascript/ai-agent-shopper": + version: 0.0.0-use.local + resolution: "ai-agent-shopper@workspace:examples/javascript/ai-agent-shopper" + dependencies: + "@ai-sdk/openai": "npm:^1.1.0" + "@clack/prompts": "npm:^0.9.1" + "@rivet-gg/actor": "workspace:*" + "@rivet-gg/actor-client": "workspace:*" + "@std/assert": "npm:@jsr/std__assert@^1.0.10" + ai: "npm:^4.1.0" + zod: "npm:^3.24.1" + languageName: unknown + linkType: soft + +"ai@npm:^4.1.0": + version: 4.1.0 + resolution: "ai@npm:4.1.0" + dependencies: + "@ai-sdk/provider": "npm:1.0.4" + "@ai-sdk/provider-utils": "npm:2.1.0" + "@ai-sdk/react": "npm:1.1.0" + "@ai-sdk/ui-utils": "npm:1.1.0" + "@opentelemetry/api": "npm:1.9.0" + jsondiffpatch: "npm:0.6.0" + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + checksum: 10c0/f8bb07c6414a2af152a12a413eb73f80feb85b8f7ff242c5d9fc0bd9d8d11488cdd507261510ffab86801e09cb9a59ebd9f2ba02ab6c890d8a19d3e74fb6513c + languageName: node + linkType: hard + "ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -4833,6 +5094,13 @@ __metadata: languageName: node linkType: hard +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + "arg@npm:^5.0.2": version: 5.0.2 resolution: "arg@npm:5.0.2" @@ -5749,6 +6017,22 @@ __metadata: languageName: node linkType: hard +"counter@workspace:examples/javascript/counter": + version: 0.0.0-use.local + resolution: "counter@workspace:examples/javascript/counter" + dependencies: + "@rivet-gg/actor": "workspace:*" + "@rivet-gg/actor-client": "workspace:*" + languageName: unknown + linkType: soft + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + "crelt@npm:^1.0.5": version: 1.0.6 resolution: "crelt@npm:1.0.6" @@ -6059,6 +6343,13 @@ __metadata: languageName: node linkType: hard +"defu@npm:^6.1.4": + version: 6.1.4 + resolution: "defu@npm:6.1.4" + checksum: 10c0/2d6cc366262dc0cb8096e429368e44052fdf43ed48e53ad84cc7c9407f890301aa5fcb80d0995abaaf842b3949f154d060be4160f7a46cb2bc2f7726c81526f5 + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -6066,7 +6357,7 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.0": +"dequal@npm:^2.0.0, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 @@ -6112,6 +6403,20 @@ __metadata: languageName: node linkType: hard +"diff-match-patch@npm:^1.0.5": + version: 1.0.5 + resolution: "diff-match-patch@npm:1.0.5" + checksum: 10c0/142b6fad627b9ef309d11bd935e82b84c814165a02500f046e2773f4ea894d10ed3017ac20454900d79d4a0322079f5b713cf0986aaf15fce0ec4a2479980c86 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + languageName: node + linkType: hard + "diff@npm:^5.0.0": version: 5.2.0 resolution: "diff@npm:5.2.0" @@ -7189,6 +7494,13 @@ __metadata: languageName: node linkType: hard +"eventsource-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "eventsource-parser@npm:3.0.0" + checksum: 10c0/74ded91ff93330bd95243bd5a8fc61c805ea1843dd7ffac8e8ac36287fff419a7eec21b2fadf50d4e554ce0506de72f47928513e3c5b886fa4613fd49ef0024f + languageName: node + linkType: hard + "expand-template@npm:^2.0.3": version: 2.0.3 resolution: "expand-template@npm:2.0.3" @@ -7705,6 +8017,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.0": + version: 11.0.1 + resolution: "glob@npm:11.0.1" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^4.0.1" + minimatch: "npm:^10.0.0" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/2b32588be52e9e90f914c7d8dec32f3144b81b84054b0f70e9adfebf37cd7014570489f2a79d21f7801b9a4bd4cca94f426966bfd00fb64a5b705cfe10da3a03 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.2.0": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -8669,6 +8997,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.0.2 + resolution: "jackspeak@npm:4.0.2" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + checksum: 10c0/b26039d11c0163a95b1e58851b9ac453cce64ad6d1eb98a00b303ad5eeb761b29d33c9419d1e16c016d3f7151c8edf7df223e6cf93a1907655fd95d6ce85c0de + languageName: node + linkType: hard + "jiti@npm:^1.21.6": version: 1.21.7 resolution: "jiti@npm:1.21.7" @@ -8754,6 +9091,13 @@ __metadata: languageName: node linkType: hard +"json-schema@npm:^0.4.0": + version: 0.4.0 + resolution: "json-schema@npm:0.4.0" + checksum: 10c0/d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3 + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -8781,6 +9125,19 @@ __metadata: languageName: node linkType: hard +"jsondiffpatch@npm:0.6.0": + version: 0.6.0 + resolution: "jsondiffpatch@npm:0.6.0" + dependencies: + "@types/diff-match-patch": "npm:^1.0.36" + chalk: "npm:^5.3.0" + diff-match-patch: "npm:^1.0.5" + bin: + jsondiffpatch: bin/jsondiffpatch.js + checksum: 10c0/f7822e48a8ef8b9f7c6024cc59b7d3707a9fe6d84fd776d169de5a1803ad551ffe7cfdc7587f3900f224bc70897355884ed43eb1c8ccd02e7f7b43a7ebcfed4f + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -9117,6 +9474,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.0.2 + resolution: "lru-cache@npm:11.0.2" + checksum: 10c0/c993b8e06ead0b24b969c1dbb5b301716aed66e320e9014a80012f5febe280b438f28ff50046b2c55ff404e889351ccb332ff91f8dd175a21f5eae80e3fb155f + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -9162,6 +9526,13 @@ __metadata: languageName: node linkType: hard +"make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + "make-fetch-happen@npm:^14.0.3": version: 14.0.3 resolution: "make-fetch-happen@npm:14.0.3" @@ -10305,6 +10676,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:^3.0.0": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: 10c0/402e792a8df1b2cc41cb77f0dcc46472b7944b7ec29cb5bbcd398624b6b97096728f1239766d3fdeb20551dd8d94738344c195a6ea10c4f906eb0356323b0531 + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -10321,6 +10701,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.0": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/e6c29a81fe83e1877ad51348306be2e8aeca18c88fdee7a99df44322314279e15799e41d7cb274e4e8bb0b451a3bc622d6182e157dfa1717d6cda75e9cd8cd5d + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -10687,6 +11076,13 @@ __metadata: languageName: node linkType: hard +"node-fetch-native@npm:^1.6.4": + version: 1.6.6 + resolution: "node-fetch-native@npm:1.6.6" + checksum: 10c0/8c12dab0e640d8bc126a03d604af9cf3fc1b87f2cda5af0c71601079d5ed835c1dc149c7042b61c83f252a382e1cf1e541788f4c9e8e6c089af77497190f5dc3 + languageName: node + linkType: hard + "node-fetch@npm:2, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -11057,6 +11453,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -12465,6 +12871,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^2.7.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 + languageName: node + linkType: hard + "semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -12740,6 +13153,13 @@ __metadata: languageName: node linkType: hard +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + "skin-tone@npm:^2.0.0": version: 2.0.0 resolution: "skin-tone@npm:2.0.0" @@ -13200,6 +13620,18 @@ __metadata: languageName: node linkType: hard +"swr@npm:^2.2.5": + version: 2.3.0 + resolution: "swr@npm:2.3.0" + dependencies: + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/192497881013654bc82d2787b60ad0701113e8ae41c511dfa8d55bcf58582657a92a4cb2854d4ea2ceaa1055e67e58daf9bd98ada2786a3035ba12898da578f1 + languageName: node + linkType: hard + "tailwind-merge@npm:^2.2.2": version: 2.6.0 resolution: "tailwind-merge@npm:2.6.0" @@ -13357,6 +13789,13 @@ __metadata: languageName: node linkType: hard +"throttleit@npm:2.1.0": + version: 2.1.0 + resolution: "throttleit@npm:2.1.0" + checksum: 10c0/1696ae849522cea6ba4f4f3beac1f6655d335e51b42d99215e196a718adced0069e48deaaf77f7e89f526ab31de5b5c91016027da182438e6f9280be2f3d5265 + languageName: node + linkType: hard + "tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -13452,6 +13891,13 @@ __metadata: languageName: node linkType: hard +"ts-dedent@npm:^2.2.0": + version: 2.2.0 + resolution: "ts-dedent@npm:2.2.0" + checksum: 10c0/175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303 + languageName: node + linkType: hard + "ts-interface-checker@npm:^0.1.9": version: 0.1.13 resolution: "ts-interface-checker@npm:0.1.13" @@ -13459,6 +13905,44 @@ __metadata: languageName: node linkType: hard +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.14.1, tsconfig-paths@npm:^3.15.0": version: 3.15.0 resolution: "tsconfig-paths@npm:3.15.0" @@ -13789,6 +14273,19 @@ __metadata: languageName: node linkType: hard +"unenv@npm:^1.10.0": + version: 1.10.0 + resolution: "unenv@npm:1.10.0" + dependencies: + consola: "npm:^3.2.3" + defu: "npm:^6.1.4" + mime: "npm:^3.0.0" + node-fetch-native: "npm:^1.6.4" + pathe: "npm:^1.1.2" + checksum: 10c0/354180647e21204b6c303339e7364b920baadb2672b540a88af267bc827636593e0bf79f59753dcc6b7ab5d4c83e71d69a9171a3596befb8bf77e0bb3c7612b9 + languageName: node + linkType: hard + "unframer@npm:=2.8.0": version: 2.8.0 resolution: "unframer@npm:2.8.0" @@ -14178,6 +14675,13 @@ __metadata: languageName: node linkType: hard +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + "validator@npm:^13.7.0": version: 13.12.0 resolution: "validator@npm:13.12.0" @@ -14694,6 +15198,13 @@ __metadata: languageName: node linkType: hard +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" @@ -14725,6 +15236,24 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.24.1": + version: 3.24.1 + resolution: "zod-to-json-schema@npm:3.24.1" + peerDependencies: + zod: ^3.24.1 + checksum: 10c0/dd4e72085003e41a3f532bd00061f27041418a4eb176aa6ce33042db08d141bd37707017ee9117d97738ae3f22fc3e1404ea44e6354634ac5da79d7d3173b4ee + languageName: node + linkType: hard + +"zod-validation-error@npm:^3.3.1": + version: 3.4.0 + resolution: "zod-validation-error@npm:3.4.0" + peerDependencies: + zod: ^3.18.0 + checksum: 10c0/aaadb0e65c834aacb12fa088663d52d9f4224b5fe6958f09b039f4ab74145fda381c8a7d470bfddf7ddd9bbb5fdfbb52739cd66958ce6d388c256a44094d1fba + languageName: node + linkType: hard + "zod@npm:^3.24, zod@npm:^3.24.1": version: 3.24.1 resolution: "zod@npm:3.24.1"