[{"data":1,"prerenderedAt":12163},["ShallowReactive",2],{"navigation_docs_en":3,"-en-developer-extensions-data-connectors-testing":445,"-en-developer-extensions-data-connectors-testing-surround":12158},[4,30,72,132,260,436],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,25],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F2.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Quickstart","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F3.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Use Cases","\u002Fen\u002Fgetting-started\u002Fuse-cases","en\u002F1.getting-started\u002F4.use-cases","i-lucide-layers",{"title":26,"path":27,"stem":28,"icon":29},"Concepts","\u002Fen\u002Fgetting-started\u002Fconcepts","en\u002F1.getting-started\u002F5.concepts","i-lucide-book",{"title":26,"path":31,"stem":32,"children":33,"page":6},"\u002Fen\u002Fconcepts","en\u002F2.concepts",[34,38,43,48,53,58,63,68],{"title":35,"path":36,"stem":37,"icon":14},"Organization","\u002Fen\u002Fconcepts\u002Forganization","en\u002F2.concepts\u002F1.organization",{"title":39,"path":40,"stem":41,"icon":42},"Data Model","\u002Fen\u002Fconcepts\u002Fdata-model","en\u002F2.concepts\u002F2.data-model","i-lucide-database",{"title":44,"path":45,"stem":46,"icon":47},"Access Control","\u002Fen\u002Fconcepts\u002Faccess-permissions","en\u002F2.concepts\u002F3.access-permissions","i-lucide-user-key",{"title":49,"path":50,"stem":51,"icon":52},"Audit Logs","\u002Fen\u002Fconcepts\u002Faudit-logs","en\u002F2.concepts\u002F4.audit-logs","i-lucide-scroll-text",{"title":54,"path":55,"stem":56,"icon":57},"Introspection","\u002Fen\u002Fconcepts\u002Fintrospection","en\u002F2.concepts\u002F6.introspection","i-lucide-database-search",{"title":59,"path":60,"stem":61,"icon":62},"Query Engine","\u002Fen\u002Fconcepts\u002Fquery-engine","en\u002F2.concepts\u002F7.query-engine","i-lucide-workflow",{"title":64,"path":65,"stem":66,"icon":67},"AI","\u002Fen\u002Fconcepts\u002Fai","en\u002F2.concepts\u002F8.ai","i-lucide-sparkles",{"title":69,"path":70,"stem":71,"icon":42},"Caching","\u002Fen\u002Fconcepts\u002Fcaching","en\u002F2.concepts\u002F9.caching",{"title":73,"path":74,"stem":75,"children":76,"page":6},"Guides","\u002Fen\u002Fguides","en\u002F4.guides",[77,82,87,92,97,102,107,112],{"title":78,"path":79,"stem":80,"icon":81},"REST API Quickstart","\u002Fen\u002Fguides\u002Frest-api","en\u002F4.guides\u002F1.rest-api","i-lucide-plug",{"title":83,"path":84,"stem":85,"icon":86},"SDK Quickstart","\u002Fen\u002Fguides\u002Fsdk","en\u002F4.guides\u002F2.sdk","i-lucide-terminal",{"title":88,"path":89,"stem":90,"icon":91},"Configure SSO","\u002Fen\u002Fguides\u002Fconfigure-sso","en\u002F4.guides\u002F3.configure-sso","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"Configure MCP","\u002Fen\u002Fguides\u002Fmcp","en\u002F4.guides\u002F4.mcp","i-lucide-brain-cog",{"title":98,"path":99,"stem":100,"icon":101},"Customize Content Space","\u002Fen\u002Fguides\u002Fcustomize-content-space","en\u002F4.guides\u002F5.customize-content-space","i-lucide-columns-3-cog",{"title":103,"path":104,"stem":105,"icon":106},"Build an Application with AI Assistance","\u002Fen\u002Fguides\u002Fbuild-an-application","en\u002F4.guides\u002F6.build-an-application","i-lucide-route",{"title":108,"path":109,"stem":110,"icon":111},"Horizontal Scaling","\u002Fen\u002Fguides\u002Fhorizontal-scaling","en\u002F4.guides\u002F7.horizontal-scaling","i-lucide-network",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":6},"Data Connectors","i-lucide-cable","\u002Fen\u002Fguides\u002Fdata-connectors","en\u002F4.guides\u002F8.data-connectors",[118,122,127],{"title":16,"path":119,"stem":120,"icon":121},"\u002Fen\u002Fguides\u002Fdata-connectors\u002Fquickstart","en\u002F4.guides\u002F8.data-connectors\u002F1.quickstart","i-lucide-rocket",{"title":123,"path":124,"stem":125,"icon":126},"Build a Stripe Connector","\u002Fen\u002Fguides\u002Fdata-connectors\u002Fstripe","en\u002F4.guides\u002F8.data-connectors\u002F2.stripe","i-lucide-hammer",{"title":128,"path":129,"stem":130,"icon":131},"Build with AI Agents","\u002Fen\u002Fguides\u002Fdata-connectors\u002Fai-agents","en\u002F4.guides\u002F8.data-connectors\u002F3.ai-agents","i-lucide-bot",{"title":133,"path":134,"stem":135,"children":136,"page":6},"Developer","\u002Fen\u002Fdeveloper","en\u002F5.developer",[137,191,215,220],{"title":138,"path":139,"stem":140,"children":141,"page":6},"Data Access","\u002Fen\u002Fdeveloper\u002Fapi","en\u002F5.developer\u002F1.api",[142,147,152,157,162,167,172,177,182,186],{"title":143,"path":144,"stem":145,"icon":146},"Overview","\u002Fen\u002Fdeveloper\u002Fapi\u002Foverview","en\u002F5.developer\u002F1.api\u002F1.overview","i-lucide-globe",{"title":148,"path":149,"stem":150,"icon":151},"Errors","\u002Fen\u002Fdeveloper\u002Fapi\u002Ferrors","en\u002F5.developer\u002F1.api\u002F10.errors","i-lucide-alert-triangle",{"title":153,"path":154,"stem":155,"icon":156},"Authentication","\u002Fen\u002Fdeveloper\u002Fapi\u002Fauthentication","en\u002F5.developer\u002F1.api\u002F2.authentication","i-lucide-lock",{"title":158,"path":159,"stem":160,"icon":161},"Reading Data","\u002Fen\u002Fdeveloper\u002Fapi\u002Freading-data","en\u002F5.developer\u002F1.api\u002F3.reading-data","i-lucide-book-open",{"title":163,"path":164,"stem":165,"icon":166},"Writing Data","\u002Fen\u002Fdeveloper\u002Fapi\u002Fwriting-data","en\u002F5.developer\u002F1.api\u002F4.writing-data","i-lucide-pencil",{"title":168,"path":169,"stem":170,"icon":171},"Filtering","\u002Fen\u002Fdeveloper\u002Fapi\u002Ffiltering","en\u002F5.developer\u002F1.api\u002F5.filtering","i-lucide-filter",{"title":173,"path":174,"stem":175,"icon":176},"Field Selection","\u002Fen\u002Fdeveloper\u002Fapi\u002Ffield-selection","en\u002F5.developer\u002F1.api\u002F6.field-selection","i-lucide-list-checks",{"title":178,"path":179,"stem":180,"icon":181},"Sorting & Pagination","\u002Fen\u002Fdeveloper\u002Fapi\u002Fsorting-pagination","en\u002F5.developer\u002F1.api\u002F7.sorting-pagination","i-lucide-arrow-up-down",{"title":183,"path":184,"stem":185,"icon":111},"Relational Data","\u002Fen\u002Fdeveloper\u002Fapi\u002Frelational-data","en\u002F5.developer\u002F1.api\u002F8.relational-data",{"title":187,"path":188,"stem":189,"icon":190},"Files & Assets","\u002Fen\u002Fdeveloper\u002Fapi\u002Ffiles","en\u002F5.developer\u002F1.api\u002F9.files","i-lucide-file-up",{"title":192,"path":193,"stem":194,"children":195,"page":6},"Client SDK","\u002Fen\u002Fdeveloper\u002Fsdk","en\u002F5.developer\u002F2.sdk",[196,200,205,210],{"title":197,"path":198,"stem":199,"icon":19},"Installation","\u002Fen\u002Fdeveloper\u002Fsdk\u002Finstallation","en\u002F5.developer\u002F2.sdk\u002F1.installation",{"title":201,"path":202,"stem":203,"icon":204},"Client Setup","\u002Fen\u002Fdeveloper\u002Fsdk\u002Fclient-setup","en\u002F5.developer\u002F2.sdk\u002F2.client-setup","i-lucide-settings",{"title":206,"path":207,"stem":208,"icon":209},"Type System","\u002Fen\u002Fdeveloper\u002Fsdk\u002Ftype-system","en\u002F5.developer\u002F2.sdk\u002F3.type-system","i-lucide-braces",{"title":211,"path":212,"stem":213,"icon":214},"Advanced","\u002Fen\u002Fdeveloper\u002Fsdk\u002Fadvanced","en\u002F5.developer\u002F2.sdk\u002F5.advanced","i-lucide-puzzle",{"title":216,"path":217,"stem":218,"icon":219},"Application Users","\u002Fen\u002Fdeveloper\u002Fapplication-users","en\u002F5.developer\u002F3.application-users","i-lucide-users",{"title":221,"path":222,"stem":223,"children":224,"icon":214},"Extensions","\u002Fen\u002Fdeveloper\u002Fextensions","en\u002F5.developer\u002F3.extensions\u002Findex",[225,226,231],{"title":143,"path":222,"stem":223,"icon":214},{"title":227,"path":228,"stem":229,"icon":230},"Install and Manage Extensions","\u002Fen\u002Fdeveloper\u002Fextensions\u002Finstall","en\u002F5.developer\u002F3.extensions\u002F1.install","i-lucide-package-plus",{"title":113,"path":232,"stem":233,"children":234,"icon":114},"\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002Findex",[235,236,241,245,250,255],{"title":143,"path":232,"stem":233,"icon":114},{"title":237,"path":238,"stem":239,"icon":240},"How Data Connectors Run","\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Fruntime","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F2.runtime","i-lucide-cpu",{"title":242,"path":243,"stem":244,"icon":42},"Data Sources","\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Fdata-sources","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F3.data-sources",{"title":246,"path":247,"stem":248,"icon":249},"Map an External Data Source","\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Fdata-source-mapping","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F4.data-source-mapping","i-lucide-map",{"title":251,"path":252,"stem":253,"icon":254},"Handle Errors","\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Ferrors","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F5.errors","i-lucide-shield-alert",{"title":256,"path":257,"stem":258,"icon":259},"Test a Data Connector","\u002Fen\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Ftesting","en\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F6.testing","i-lucide-flask-conical",{"title":261,"path":262,"stem":263,"children":264,"page":6},"Reference","\u002Fen\u002Freference","en\u002F6.reference",[265,269,274,279,295,299,304,340,369,374],{"title":266,"path":267,"stem":268,"icon":204},"Configuration","\u002Fen\u002Freference\u002Fenvironment-variables","en\u002F6.reference\u002F1.environment-variables",{"title":270,"path":271,"stem":272,"icon":273},"System Requirements","\u002Fen\u002Freference\u002Fsystem-requirements","en\u002F6.reference\u002F2.system-requirements","i-lucide-server",{"title":275,"path":276,"stem":277,"icon":278},"Licensing","\u002Fen\u002Freference\u002Flicensing","en\u002F6.reference\u002F3.licensing","i-lucide-gem",{"title":280,"path":281,"stem":282,"children":283,"icon":81},"Connectors","\u002Fen\u002Freference\u002Fconnectors","en\u002F6.reference\u002F4.connectors\u002Findex",[284,285,290],{"title":280,"path":281,"stem":282,"icon":81},{"title":286,"path":287,"stem":288,"icon":289},"PostgreSQL and Supabase","\u002Fen\u002Freference\u002Fconnectors\u002Fpostgresql-supabase","en\u002F6.reference\u002F4.connectors\u002F1.postgresql-supabase","i-simple-icons-postgresql",{"title":291,"path":292,"stem":293,"icon":294},"MySQL and MariaDB","\u002Fen\u002Freference\u002Fconnectors\u002Fmysql-mariadb","en\u002F6.reference\u002F4.connectors\u002F2.mysql-mariadb","i-simple-icons-mysql",{"title":296,"path":297,"stem":298,"icon":209},"Data Types Representation","\u002Fen\u002Freference\u002Fdata-types","en\u002F6.reference\u002F5.data-types",{"title":300,"path":301,"stem":302,"icon":303},"Permissions Reference","\u002Fen\u002Freference\u002Fpermissions","en\u002F6.reference\u002F6.permissions","i-lucide-shield",{"title":305,"path":306,"stem":307,"children":308,"page":6},"API Reference","\u002Fen\u002Freference\u002Fapi-reference","en\u002F6.reference\u002F7.api-reference",[309,313,318],{"title":310,"path":311,"stem":312,"icon":273},"System Endpoints","\u002Fen\u002Freference\u002Fapi-reference\u002Fsystem-endpoints","en\u002F6.reference\u002F7.api-reference\u002F1.system-endpoints",{"title":314,"path":315,"stem":316,"icon":317},"OpenAPI Spec","\u002Fen\u002Freference\u002Fapi-reference\u002Fopenapi-spec","en\u002F6.reference\u002F7.api-reference\u002F2.openapi-spec","i-lucide-file-code",{"title":319,"path":320,"stem":321,"children":322,"icon":324},"Schema Migration","\u002Fen\u002Freference\u002Fapi-reference\u002Fschema-migration","en\u002F6.reference\u002F7.api-reference\u002F3.schema-migration\u002Findex",[323,325,330,335],{"title":319,"path":320,"stem":321,"icon":324},"i-lucide-database-zap",{"title":326,"path":327,"stem":328,"icon":329},"Apply Migrations","\u002Fen\u002Freference\u002Fapi-reference\u002Fschema-migration\u002Fapply-migrations","en\u002F6.reference\u002F7.api-reference\u002F3.schema-migration\u002F1.apply-migrations","i-lucide-upload",{"title":331,"path":332,"stem":333,"icon":334},"Import Changes","\u002Fen\u002Freference\u002Fapi-reference\u002Fschema-migration\u002Fimport-changes","en\u002F6.reference\u002F7.api-reference\u002F3.schema-migration\u002F2.import-changes","i-lucide-refresh-cw",{"title":336,"path":337,"stem":338,"icon":339},"Operation Reference","\u002Fen\u002Freference\u002Fapi-reference\u002Fschema-migration\u002Foperation-reference","en\u002F6.reference\u002F7.api-reference\u002F3.schema-migration\u002F3.operation-reference","i-lucide-list-tree",{"title":341,"path":342,"stem":343,"children":344,"page":6},"Design Considerations","\u002Fen\u002Freference\u002Fdesign-considerations","en\u002F6.reference\u002F8.design-considerations",[345,349,354,359,364],{"title":143,"path":346,"stem":347,"icon":348},"\u002Fen\u002Freference\u002Fdesign-considerations\u002Foverview","en\u002F6.reference\u002F8.design-considerations\u002F1.overview","i-lucide-compass",{"title":350,"path":351,"stem":352,"icon":353},"Instance Configuration","\u002Fen\u002Freference\u002Fdesign-considerations\u002Finstance-configuration-related-problems","en\u002F6.reference\u002F8.design-considerations\u002F3.instance-configuration-related-problems","i-lucide-wrench",{"title":355,"path":356,"stem":357,"icon":358},"Overly Permissive Public Access","\u002Fen\u002Freference\u002Fdesign-considerations\u002Foverly-permissive","en\u002F6.reference\u002F8.design-considerations\u002F4.overly-permissive","i-lucide-shield-off",{"title":360,"path":361,"stem":362,"icon":363},"No Aggregates","\u002Fen\u002Freference\u002Fdesign-considerations\u002Fno-aggregates","en\u002F6.reference\u002F8.design-considerations\u002F5.no-aggregates","i-lucide-sigma",{"title":365,"path":366,"stem":367,"icon":368},"Cross-Data-Source Referential Integrity","\u002Fen\u002Freference\u002Fdesign-considerations\u002Fcross-data-source-referential-integrity","en\u002F6.reference\u002F8.design-considerations\u002F6.cross-data-source-referential-integrity","i-lucide-unlink",{"title":370,"path":371,"stem":372,"icon":373},"CLI","\u002Fen\u002Freference\u002Fcli","en\u002F6.reference\u002F9.cli","i-lucide-square-terminal",{"title":221,"path":375,"stem":376,"children":377,"page":6},"\u002Fen\u002Freference\u002Fextensions","en\u002F6.reference\u002F9.extensions",[378,383,386,405],{"title":379,"path":380,"stem":381,"icon":382},"Extension Config and Manifest","\u002Fen\u002Freference\u002Fextensions\u002Fmanifest","en\u002F6.reference\u002F9.extensions\u002F1.manifest","i-lucide-file-json",{"title":370,"path":384,"stem":385,"icon":86},"\u002Fen\u002Freference\u002Fextensions\u002Fcli","en\u002F6.reference\u002F9.extensions\u002F2.cli",{"title":387,"path":388,"stem":389,"children":390,"page":6},"Extension Kit","\u002Fen\u002Freference\u002Fextensions\u002Fextension-kit","en\u002F6.reference\u002F9.extensions\u002F3.extension-kit",[391,395,400],{"title":392,"path":393,"stem":394,"icon":81},"Data Connector","\u002Fen\u002Freference\u002Fextensions\u002Fextension-kit\u002Fdata-connector","en\u002F6.reference\u002F9.extensions\u002F3.extension-kit\u002F1.data-connector",{"title":396,"path":397,"stem":398,"icon":399},"Protocol","\u002Fen\u002Freference\u002Fextensions\u002Fextension-kit\u002Fprotocol","en\u002F6.reference\u002F9.extensions\u002F3.extension-kit\u002F2.protocol","i-lucide-arrow-left-right",{"title":401,"path":402,"stem":403,"icon":404},"Schema Helpers","\u002Fen\u002Freference\u002Fextensions\u002Fextension-kit\u002Fexperimental","en\u002F6.reference\u002F9.extensions\u002F3.extension-kit\u002F3.experimental","i-lucide-blocks",{"title":113,"path":406,"stem":407,"children":408,"page":6},"\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors",[409,413,417,421,426,431],{"title":410,"path":411,"stem":412,"icon":81},"Data Connector API","\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Fapi","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F1.api",{"title":414,"path":415,"stem":416,"icon":176},"Operations and Operators","\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Foperations","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F2.operations",{"title":148,"path":418,"stem":419,"icon":420},"\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Ferrors","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F3.errors","i-lucide-triangle-alert",{"title":422,"path":423,"stem":424,"icon":425},"Runtime Limits","\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Flimits","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F4.limits","i-lucide-gauge",{"title":427,"path":428,"stem":429,"icon":430},"Limitations","\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Flimitations","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F5.limitations","i-lucide-ban",{"title":432,"path":433,"stem":434,"icon":435},"Network Permissions","\u002Fen\u002Freference\u002Fextensions\u002Fdata-connectors\u002Fpermissions","en\u002F6.reference\u002F9.extensions\u002F4.data-connectors\u002F6.permissions","i-lucide-shield-check",{"title":437,"icon":121,"path":438,"stem":439,"children":440,"page":6},"Release Notes","\u002Fen\u002Frelease-notes","en\u002F7.release-notes",[441],{"title":442,"path":443,"stem":444,"icon":121},"Changelog","\u002Fen\u002Frelease-notes\u002Fchangelog","en\u002F7.release-notes\u002F1.changelog",{"id":446,"title":256,"body":447,"description":12151,"extension":12152,"links":12153,"meta":12154,"navigation":12155,"path":257,"seo":12156,"stem":258,"__hash__":12157},"docs_en\u002Fen\u002F5.developer\u002F3.extensions\u002F2.data-connectors\u002F6.testing.md",{"type":448,"value":449,"toc":12123},"minimark",[450,453,457,477,488,514,647,650,657,782,786,789,880,883,937,1312,1318,1329,1332,2449,2454,2457,2477,2485,2489,2495,2555,2606,2657,2663,2921,2924,2992,2998,3904,3907,4502,4505,5151,5158,5172,5175,5200,5251,5260,5280,5294,5298,5304,5355,5406,5412,6213,6247,6252,6256,6458,6461,7167,7211,7217,7224,7230,7233,7236,7252,7753,7769,8166,8172,8175,8179,8184,8383,8386,8390,8420,8437,8450,8454,8461,8469,8541,8552,8558,8699,8706,8709,8713,8716,8722,8734,8744,8810,8861,8878,10233,10236,10951,10966,10969,11555,11562,11566,11569,11572,11795,11798,11801,11853,11856,11859,11863,11866,11869,11948,11954,11957,11961,11964,12001,12004,12007,12011,12014,12082,12086,12119],[451,452],"preview",{},[454,455,143],"h2",{"id":456},"overview",[458,459,460,461,465,466,471,472,476],"p",{},"A data connector extension can pass its own tests and still fail in Engine. The build doesn't typecheck, and Engine doesn't check the declarations your build returns until a ",[462,463,464],"strong",{},"data source"," (the connector configured in a ",[467,468,470],"a",{"href":469},"\u002Fconcepts\u002Forganization#glossary","workspace",") is created or reintrospected. It checks them again whenever it loads the stored schema, so an Engine upgrade can disable a declaration that passed before; see ",[467,473,475],{"href":474},"\u002Freference\u002Fextensions\u002Fdata-connectors\u002Flimitations#engine-upgrades-can-disable-connector-collections","Engine Upgrades Can Disable Connector Collections",".",[458,478,479,480,483,484,476],{},"A lenient test fixture hides bugs too. It can answer a wrong request to the ",[462,481,482],{},"external data source"," (the API, service, or database the connector calls) with the right ",[467,485,487],{"href":486},"\u002Fconcepts\u002Fdata-model#collections","items",[458,489,490,491,495,496,499,500,504,505,509,510,513],{},"The ladder below runs from cheap to expensive. Each rung catches bugs the rungs before it can't. The snippets come from the Stripe connector in ",[467,492,494],{"href":493},"\u002Fguides\u002Fdata-connectors\u002Fstripe","Build a Stripe Data Connector"," and the full Art Institute of Chicago connector from ",[467,497,246],{"href":498},"\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Fdata-source-mapping",", which the ",[467,501,503],{"href":502},"\u002Fguides\u002Fdata-connectors\u002Fquickstart","Data Connector Quickstart"," trims. A file's first excerpt starts at the top of the file, and later excerpts of the same file continue it; ",[506,507,508],"code",{},"\u002F\u002F …"," or ",[506,511,512],{},"# …"," marks the lines an excerpt leaves out.",[515,516,517,533],"table",{},[518,519,520],"thead",{},[521,522,523,527,530],"tr",{},[524,525,526],"th",{},"Rung",[524,528,529],{},"Catches",[524,531,532],{},"Misses",[534,535,536,551,565,579,593,607,620,634],"tbody",{},[521,537,538,545,548],{},[539,540,541],"td",{},[467,542,544],{"href":543},"#_1-lint-typecheck-and-run-unit-tests","1. Lint, typecheck, and unit tests",[539,546,547],{},"Type errors, wrong kit types, planning and error-mapping bugs, failures a live account can't produce on demand",[539,549,550],{},"The bundle, the live external data source",[521,552,553,559,562],{},[539,554,555],{},[467,556,558],{"href":557},"#_2-build","2. Build",[539,560,561],{},"Imports that don't bundle, a missing default export",[539,563,564],{},"Whether the connector works",[521,566,567,573,576],{},[539,568,569],{},[467,570,572],{"href":571},"#_3-run-an-artifact-smoke","3. Artifact smoke",[539,574,575],{},"Wrong reads, paging, pushdown, refusals, error classes, keyed operations",[539,577,578],{},"Engine's declaration rules, the sandbox",[521,580,581,587,590],{},[539,582,583],{},[467,584,586],{"href":585},"#_4-install-and-check-the-catalog","4. Install and catalog",[539,588,589],{},"A bundle that loads without your connector",[539,591,592],{},"Declarations Engine refuses",[521,594,595,601,604],{},[539,596,597],{},[467,598,600],{"href":599},"#_5-run-an-engine-smoke","5. Engine smoke",[539,602,603],{},"Declarations Engine refuses, narrowed relations, permissions, everything between REST and your code",[539,605,606],{},"Checks that can't fail",[521,608,609,615,617],{},[539,610,611],{},[467,612,614],{"href":613},"#_6-break-each-check-on-purpose","6. Mutation checks",[539,616,606],{},[539,618,619],{},"Residue",[521,621,622,628,631],{},[539,623,624],{},[467,625,627],{"href":626},"#_7-run-the-ladder-twice","7. Run twice",[539,629,630],{},"Residue from an earlier run",[539,632,633],{},"What Studio offers",[521,635,636,642,645],{},[539,637,638],{},[467,639,641],{"href":640},"#_8-try-it-in-studio","8. Studio",[539,643,644],{},"Controls that fail with messages users can't act on",[539,646],{},[458,648,649],{},"Reach rung 5 early, before you write query code. A declaration Engine refuses is cheaper to fix before planning code depends on it.",[458,651,652,653,656],{},"The example projects have a ",[506,654,655],{},"package.json"," script for lint, typecheck and unit tests, the build, the artifact smoke and the Engine smoke. Installing, breaking checks on purpose and trying Studio have no script:",[515,658,659,672],{},[518,660,661],{},[521,662,663,666,669],{},[524,664,665],{},"Script",[524,667,668],{},"What it runs",[524,670,671],{},"What it asserts",[534,673,674,691,706,719,734,750,766],{},[521,675,676,681,688],{},[539,677,678],{},[506,679,680],{},"lint",[539,682,683,684,687],{},"ESLint with ",[506,685,686],{},"typescript-eslint","'s strict type-checked rules",[539,689,690],{},"No lint errors",[521,692,693,698,703],{},[539,694,695],{},[506,696,697],{},"typecheck",[539,699,700],{},[506,701,702],{},"tsc --noEmit",[539,704,705],{},"No type errors",[521,707,708,713,716],{},[539,709,710],{},[506,711,712],{},"test",[539,714,715],{},"Vitest, with no network",[539,717,718],{},"Every unit test passes: 110 for Stripe, 31 for the full Art Institute connector",[521,720,721,726,731],{},[539,722,723],{},[506,724,725],{},"build",[539,727,728],{},[506,729,730],{},"monospace extension build",[539,732,733],{},"A bundle and a manifest",[521,735,736,741,747],{},[539,737,738],{},[506,739,740],{},"seed",[539,742,743,746],{},[506,744,745],{},"scripts\u002Fseed.mjs"," (Stripe only)",[539,748,749],{},"Creates the fixture items that are missing",[521,751,752,757,763],{},[539,753,754],{},[506,755,756],{},"smoke",[539,758,759,762],{},[506,760,761],{},"scripts\u002Fsmoke.mjs",", against the built file and the live external data source",[539,764,765],{},"Every named check passes: 19 for Stripe, 19 for the full Art Institute connector",[521,767,768,773,779],{},[539,769,770],{},[506,771,772],{},"smoke:engine",[539,774,775,778],{},[506,776,777],{},"scripts\u002Fengine-smoke.sh",", against a running instance (a Monospace deployment)",[539,780,781],{},"Every status and body check passes: 47 for Stripe, 31 for the full Art Institute connector",[454,783,785],{"id":784},"choose-a-fixture","Choose a Fixture",[458,787,788],{},"Test against an external data source you control, with data you know. Never test against production.",[515,790,791,804],{},[518,792,793],{},[521,794,795,798,801],{},[524,796,797],{},"Fixture",[524,799,800],{},"Real behavior",[524,802,803],{},"Use when",[534,805,806,817,827,837,847,858],{},[521,807,808,811,814],{},[539,809,810],{},"Test-mode account (e.g., Stripe test mode)",[539,812,813],{},"Yes",[539,815,816],{},"The vendor offers one",[521,818,819,822,824],{},[539,820,821],{},"Shared real account, namespaced",[539,823,813],{},[539,825,826],{},"A real account exists but isn't disposable",[521,828,829,832,834],{},[539,830,831],{},"Self-hosted external data source in Docker, with a seed",[539,833,813],{},[539,835,836],{},"The external data source runs locally",[521,838,839,842,844],{},[539,840,841],{},"Public read-only API (e.g., the Art Institute of Chicago API)",[539,843,813],{},[539,845,846],{},"The external data source needs no account. Compare with its live answers, never hard-coded totals",[521,848,849,852,855],{},[539,850,851],{},"Emulator",[539,853,854],{},"No",[539,856,857],{},"You need state (writes, relations) without credentials",[521,859,860,867,870],{},[539,861,862,863,866],{},"Stand-in: a fake ",[506,864,865],{},"fetch"," or a small HTTP server that answers as you probed",[539,868,869],{},"Only what you probed",[539,871,872,873,876,877],{},"You need a behavior no fixture reproduces, or a fault such as a timeout, a ",[506,874,875],{},"429"," or a ",[506,878,879],{},"5xx",[458,881,882],{},"For every fixture:",[884,885,886,913,919,931],"ul",{},[887,888,889,892,893,896,897,900,901,904,905,908,909,912],"li",{},[462,890,891],{},"Namespace what you create."," The Stripe fixture uses an email domain (",[506,894,895],{},"@stripe-fixture.monospace.test",") and custom product ids (",[506,898,899],{},"prod_msfx_*","). The Engine smoke also creates and deletes products named ",[506,902,903],{},"prod_docs_smoke_\u003Ctimestamp>_*",", and creates prices, with ids Stripe assigns, on the fixture product ",[506,906,907],{},"prod_msfx_locked",". Writes touch only those items. Paging checks read the account's customers without filtering them to the fixture, and compare with Stripe's own answer. The ",[506,910,911],{},"limit"," 150 check needs more than 100 customers in the account, which the seed doesn't create.",[887,914,915,918],{},[462,916,917],{},"Compare with the external data source, not with totals."," On a shared account, other items come and go. Ask the external data source the same question and compare.",[887,920,921,924,925,927,928,476],{},[462,922,923],{},"Seed the fixture from a script."," The ",[506,926,740],{}," script creates the Stripe fixture's three customers, three products and their prices in a test-mode account. It creates only what's missing, and looks for existing prices among each product's first 100, so a second run creates nothing and prints ",[506,929,930],{},"{\"ok\":true,\"created\":[]}",[887,932,933,936],{},[462,934,935],{},"Refuse production credentials in your scripts."," The Stripe seed and smoke scripts load the key through a helper that accepts only test-mode keys:",[938,939,945],"pre",{"className":940,"code":941,"filename":942,"language":943,"meta":944,"style":944},"language-js shiki shiki-themes monospace-light monospace-light monospace-dark","\u002F\u002F Shared by the seed and smoke scripts: loads a test-mode key and talks to Stripe directly.\nimport { readFileSync } from 'node:fs';\nimport process from 'node:process';\n\n\u002F** Must match `STRIPE_API_VERSION` in collections.ts. The smoke checks that it does. *\u002F\nexport const STRIPE_API_VERSION = '2026-08-26.dahlia';\nexport const FIXTURE_EMAIL_DOMAIN = 'stripe-fixture.monospace.test';\nexport const FIXTURE_MARKER = 'stripe-connector-fixture-v1';\n\n\u002F** `STRIPE_API_KEY`, or the file in `STRIPE_API_KEY_FILE`, which holds only the key. Surrounding whitespace is trimmed. *\u002F\nexport function loadApiKey() {\n    const file = process.env.STRIPE_API_KEY_FILE;\n    const key = process.env.STRIPE_API_KEY?.trim() ?? (file === undefined ? undefined : readFileSync(file, 'utf8').trim());\n    if (key === undefined || key === '') throw new Error('Set STRIPE_API_KEY or STRIPE_API_KEY_FILE to a Stripe test-mode key.');\n    \u002F\u002F These scripts write to the account: never point them at live data.\n    if (!\u002F^(?:sk|rk)_test_\u002Fu.test(key)) throw new Error('The Stripe scripts only accept a test-mode key.');\n    return key;\n}\n","scripts\u002Fstripe.mjs","js","",[506,946,947,956,984,1000,1007,1013,1034,1051,1068,1073,1079,1093,1118,1193,1238,1244,1296,1306],{"__ignoreMap":944},[948,949,952],"span",{"class":950,"line":951},"line",1,[948,953,955],{"class":954},"seNfP","\u002F\u002F Shared by the seed and smoke scripts: loads a test-mode key and talks to Stripe directly.\n",[948,957,959,963,967,971,974,977,981],{"class":950,"line":958},2,[948,960,962],{"class":961},"sLVBU","import",[948,964,966],{"class":965},"sTMul"," { ",[948,968,970],{"class":969},"sNJ9c","readFileSync",[948,972,973],{"class":965}," } ",[948,975,976],{"class":961},"from",[948,978,980],{"class":979},"suKVh"," 'node:fs'",[948,982,983],{"class":965},";\n",[948,985,987,989,992,995,998],{"class":950,"line":986},3,[948,988,962],{"class":961},[948,990,991],{"class":969}," process",[948,993,994],{"class":961}," from",[948,996,997],{"class":979}," 'node:process'",[948,999,983],{"class":965},[948,1001,1003],{"class":950,"line":1002},4,[948,1004,1006],{"emptyLinePlaceholder":1005},true,"\n",[948,1008,1010],{"class":950,"line":1009},5,[948,1011,1012],{"class":954},"\u002F** Must match `STRIPE_API_VERSION` in collections.ts. The smoke checks that it does. *\u002F\n",[948,1014,1016,1019,1022,1026,1029,1032],{"class":950,"line":1015},6,[948,1017,1018],{"class":961},"export",[948,1020,1021],{"class":961}," const",[948,1023,1025],{"class":1024},"ssBNi"," STRIPE_API_VERSION",[948,1027,1028],{"class":961}," =",[948,1030,1031],{"class":979}," '2026-08-26.dahlia'",[948,1033,983],{"class":965},[948,1035,1037,1039,1041,1044,1046,1049],{"class":950,"line":1036},7,[948,1038,1018],{"class":961},[948,1040,1021],{"class":961},[948,1042,1043],{"class":1024}," FIXTURE_EMAIL_DOMAIN",[948,1045,1028],{"class":961},[948,1047,1048],{"class":979}," 'stripe-fixture.monospace.test'",[948,1050,983],{"class":965},[948,1052,1054,1056,1058,1061,1063,1066],{"class":950,"line":1053},8,[948,1055,1018],{"class":961},[948,1057,1021],{"class":961},[948,1059,1060],{"class":1024}," FIXTURE_MARKER",[948,1062,1028],{"class":961},[948,1064,1065],{"class":979}," 'stripe-connector-fixture-v1'",[948,1067,983],{"class":965},[948,1069,1071],{"class":950,"line":1070},9,[948,1072,1006],{"emptyLinePlaceholder":1005},[948,1074,1076],{"class":950,"line":1075},10,[948,1077,1078],{"class":954},"\u002F** `STRIPE_API_KEY`, or the file in `STRIPE_API_KEY_FILE`, which holds only the key. Surrounding whitespace is trimmed. *\u002F\n",[948,1080,1082,1084,1087,1090],{"class":950,"line":1081},11,[948,1083,1018],{"class":961},[948,1085,1086],{"class":961}," function",[948,1088,1089],{"class":1024}," loadApiKey",[948,1091,1092],{"class":965},"() {\n",[948,1094,1096,1099,1102,1104,1106,1108,1111,1113,1116],{"class":950,"line":1095},12,[948,1097,1098],{"class":961},"    const",[948,1100,1101],{"class":1024}," file",[948,1103,1028],{"class":961},[948,1105,991],{"class":969},[948,1107,476],{"class":965},[948,1109,1110],{"class":969},"env",[948,1112,476],{"class":965},[948,1114,1115],{"class":1024},"STRIPE_API_KEY_FILE",[948,1117,983],{"class":965},[948,1119,1121,1123,1126,1128,1130,1132,1134,1136,1139,1142,1145,1148,1151,1154,1157,1160,1163,1166,1168,1171,1174,1177,1179,1182,1185,1188,1190],{"class":950,"line":1120},13,[948,1122,1098],{"class":961},[948,1124,1125],{"class":1024}," key",[948,1127,1028],{"class":961},[948,1129,991],{"class":969},[948,1131,476],{"class":965},[948,1133,1110],{"class":969},[948,1135,476],{"class":965},[948,1137,1138],{"class":1024},"STRIPE_API_KEY",[948,1140,1141],{"class":965},"?.",[948,1143,1144],{"class":1024},"trim",[948,1146,1147],{"class":965},"() ",[948,1149,1150],{"class":961},"??",[948,1152,1153],{"class":965}," (",[948,1155,1156],{"class":969},"file",[948,1158,1159],{"class":961}," ===",[948,1161,1162],{"class":1024}," undefined",[948,1164,1165],{"class":961}," ?",[948,1167,1162],{"class":1024},[948,1169,1170],{"class":961}," :",[948,1172,1173],{"class":1024}," readFileSync",[948,1175,1176],{"class":965},"(",[948,1178,1156],{"class":969},[948,1180,1181],{"class":965},", ",[948,1183,1184],{"class":979},"'utf8'",[948,1186,1187],{"class":965},").",[948,1189,1144],{"class":1024},[948,1191,1192],{"class":965},"());\n",[948,1194,1196,1199,1201,1204,1206,1208,1211,1213,1215,1218,1221,1224,1227,1230,1232,1235],{"class":950,"line":1195},14,[948,1197,1198],{"class":961},"    if",[948,1200,1153],{"class":965},[948,1202,1203],{"class":969},"key",[948,1205,1159],{"class":961},[948,1207,1162],{"class":1024},[948,1209,1210],{"class":961}," ||",[948,1212,1125],{"class":969},[948,1214,1159],{"class":961},[948,1216,1217],{"class":979}," ''",[948,1219,1220],{"class":965},") ",[948,1222,1223],{"class":961},"throw",[948,1225,1226],{"class":961}," new",[948,1228,1229],{"class":1024}," Error",[948,1231,1176],{"class":965},[948,1233,1234],{"class":979},"'Set STRIPE_API_KEY or STRIPE_API_KEY_FILE to a Stripe test-mode key.'",[948,1236,1237],{"class":965},");\n",[948,1239,1241],{"class":950,"line":1240},15,[948,1242,1243],{"class":954},"    \u002F\u002F These scripts write to the account: never point them at live data.\n",[948,1245,1247,1249,1251,1254,1257,1260,1263,1266,1269,1272,1274,1276,1278,1280,1283,1285,1287,1289,1291,1294],{"class":950,"line":1246},16,[948,1248,1198],{"class":961},[948,1250,1153],{"class":965},[948,1252,1253],{"class":961},"!",[948,1255,1256],{"class":979},"\u002F",[948,1258,1259],{"class":961},"^",[948,1261,1262],{"class":979},"(?:sk",[948,1264,1265],{"class":961},"|",[948,1267,1268],{"class":979},"rk)_test_\u002F",[948,1270,1271],{"class":961},"u",[948,1273,476],{"class":965},[948,1275,712],{"class":1024},[948,1277,1176],{"class":965},[948,1279,1203],{"class":969},[948,1281,1282],{"class":965},")) ",[948,1284,1223],{"class":961},[948,1286,1226],{"class":961},[948,1288,1229],{"class":1024},[948,1290,1176],{"class":965},[948,1292,1293],{"class":979},"'The Stripe scripts only accept a test-mode key.'",[948,1295,1237],{"class":965},[948,1297,1299,1302,1304],{"class":950,"line":1298},17,[948,1300,1301],{"class":961},"    return",[948,1303,1125],{"class":969},[948,1305,983],{"class":965},[948,1307,1309],{"class":950,"line":1308},18,[948,1310,1311],{"class":965},"}\n",[458,1313,1314,1315],{},"The Engine smoke checks the key itself before it sends any request, to the instance or to Stripe: ",[506,1316,1317],{},"[[ $KEY =~ ^(sk|rk)_test_ ]] || { echo 'Use a Stripe test-mode key.' >&2; exit 1; }",[884,1319,1320],{},[887,1321,1322,1325,1326,1328],{},[462,1323,1324],{},"Plan for what the external data source can't delete."," Stripe can't delete prices, or a product that still has prices. Each run of the Stripe Engine smoke leaves one archived price on ",[506,1327,907],{},"; the customer and the three products it creates are deleted.",[458,1330,1331],{},"The seed script looks each fixture item up before it creates it:",[1333,1334,1335],"accordion",{},[1336,1337,1338],"accordion-item",{"label":745},[938,1339,1341],{"className":940,"code":1340,"filename":745,"language":943,"meta":944,"style":944},"\u002F\u002F Creates the fixture the smoke scripts read, in a Stripe test-mode account. Idempotent: it\n\u002F\u002F creates only what is missing, found by email for customers and by id and nickname for products\n\u002F\u002F and prices. Every fixture object carries `metadata[fixture]` so it can be told apart.\nimport { FIXTURE_EMAIL_DOMAIN, FIXTURE_MARKER, loadApiKey, stripeApi } from '.\u002Fstripe.mjs';\n\nconst stripe = stripeApi(loadApiKey());\n\nconst customers = [\n    { key: 'ada', name: 'Ada Lovelace', phone: '+4930123456', description: 'Fixture customer with payments' },\n    { key: 'grace', name: 'Grace Hopper', phone: '+4930654321', description: 'Fixture customer with a refund and a subscription' },\n    { key: 'linus', name: 'Linus Torvalds', description: 'Fixture customer with a draft invoice' },\n];\n\nconst products = [\n    { id: 'prod_msfx_starter', name: 'Fixture Starter', description: 'Starter plan', prices: [\n        { nickname: 'msfx_starter_monthly', unit_amount: '900', interval: 'month' },\n    ] },\n    { id: 'prod_msfx_pro', name: 'Fixture Pro', description: 'Pro plan', prices: [\n        { nickname: 'msfx_pro_monthly', unit_amount: '2900', interval: 'month' },\n        { nickname: 'msfx_pro_setup', unit_amount: '5000' },\n    ] },\n    \u002F\u002F A product with a price can't be deleted: the smokes use this one for that refusal.\n    { id: 'prod_msfx_locked', name: 'Fixture Locked', description: 'Cannot be deleted', prices: [\n        { nickname: 'msfx_locked', unit_amount: '100' },\n    ] },\n];\n\nconst created = [];\n\nfor (const { key, ...fields } of customers) {\n    const email = `${key}@${FIXTURE_EMAIL_DOMAIN}`;\n    const [existing] = await stripe.list('customers', { email, limit: '1' });\n    if (existing !== undefined) continue;\n\n    await stripe.post('customers', { ...fields, email, 'metadata[fixture]': FIXTURE_MARKER, 'metadata[fixture_key]': key });\n    created.push(email);\n}\n\nfor (const { id, prices, ...fields } of products) {\n    const existing = await stripe.get(`products\u002F${id}`).catch((error) => {\n        if (error.status === 404) return undefined;\n        throw error;\n    });\n\n    if (existing === undefined) {\n        await stripe.post('products', { id, ...fields, 'metadata[fixture]': FIXTURE_MARKER, 'metadata[fixture_key]': id });\n        created.push(id);\n    }\n\n    const current = await stripe.list('prices', { product: id, limit: '100' });\n\n    for (const { nickname, unit_amount, interval } of prices) {\n        if (current.some((price) => price.nickname === nickname)) continue;\n\n        await stripe.post('prices', {\n            product: id,\n            currency: 'eur',\n            unit_amount,\n            nickname,\n            ...(interval === undefined ? {} : { 'recurring[interval]': interval }),\n            'metadata[fixture]': FIXTURE_MARKER,\n            'metadata[fixture_key]': nickname,\n        });\n        created.push(nickname);\n    }\n}\n\nprocess.stdout.write(`${JSON.stringify({ ok: true, created })}\\n`);\n",[506,1342,1343,1348,1353,1358,1391,1395,1414,1418,1430,1459,1483,1502,1507,1511,1522,1543,1565,1570,1589,1608,1623,1628,1634,1654,1669,1674,1679,1684,1697,1702,1734,1759,1806,1827,1832,1880,1897,1902,1907,1940,1987,2016,2027,2033,2038,2053,2098,2114,2120,2125,2159,2164,2198,2240,2245,2263,2274,2285,2293,2301,2335,2347,2359,2365,2380,2385,2390,2395],{"__ignoreMap":944},[948,1344,1345],{"class":950,"line":951},[948,1346,1347],{"class":954},"\u002F\u002F Creates the fixture the smoke scripts read, in a Stripe test-mode account. Idempotent: it\n",[948,1349,1350],{"class":950,"line":958},[948,1351,1352],{"class":954},"\u002F\u002F creates only what is missing, found by email for customers and by id and nickname for products\n",[948,1354,1355],{"class":950,"line":986},[948,1356,1357],{"class":954},"\u002F\u002F and prices. Every fixture object carries `metadata[fixture]` so it can be told apart.\n",[948,1359,1360,1362,1364,1367,1369,1372,1374,1377,1379,1382,1384,1386,1389],{"class":950,"line":1002},[948,1361,962],{"class":961},[948,1363,966],{"class":965},[948,1365,1366],{"class":969},"FIXTURE_EMAIL_DOMAIN",[948,1368,1181],{"class":965},[948,1370,1371],{"class":969},"FIXTURE_MARKER",[948,1373,1181],{"class":965},[948,1375,1376],{"class":969},"loadApiKey",[948,1378,1181],{"class":965},[948,1380,1381],{"class":969},"stripeApi",[948,1383,973],{"class":965},[948,1385,976],{"class":961},[948,1387,1388],{"class":979}," '.\u002Fstripe.mjs'",[948,1390,983],{"class":965},[948,1392,1393],{"class":950,"line":1009},[948,1394,1006],{"emptyLinePlaceholder":1005},[948,1396,1397,1400,1403,1405,1408,1410,1412],{"class":950,"line":1015},[948,1398,1399],{"class":961},"const",[948,1401,1402],{"class":1024}," stripe",[948,1404,1028],{"class":961},[948,1406,1407],{"class":1024}," stripeApi",[948,1409,1176],{"class":965},[948,1411,1376],{"class":1024},[948,1413,1192],{"class":965},[948,1415,1416],{"class":950,"line":1036},[948,1417,1006],{"emptyLinePlaceholder":1005},[948,1419,1420,1422,1425,1427],{"class":950,"line":1053},[948,1421,1399],{"class":961},[948,1423,1424],{"class":1024}," customers",[948,1426,1028],{"class":961},[948,1428,1429],{"class":965}," [\n",[948,1431,1432,1435,1438,1441,1444,1447,1450,1453,1456],{"class":950,"line":1070},[948,1433,1434],{"class":965},"    { key: ",[948,1436,1437],{"class":979},"'ada'",[948,1439,1440],{"class":965},", name: ",[948,1442,1443],{"class":979},"'Ada Lovelace'",[948,1445,1446],{"class":965},", phone: ",[948,1448,1449],{"class":979},"'+4930123456'",[948,1451,1452],{"class":965},", description: ",[948,1454,1455],{"class":979},"'Fixture customer with payments'",[948,1457,1458],{"class":965}," },\n",[948,1460,1461,1463,1466,1468,1471,1473,1476,1478,1481],{"class":950,"line":1075},[948,1462,1434],{"class":965},[948,1464,1465],{"class":979},"'grace'",[948,1467,1440],{"class":965},[948,1469,1470],{"class":979},"'Grace Hopper'",[948,1472,1446],{"class":965},[948,1474,1475],{"class":979},"'+4930654321'",[948,1477,1452],{"class":965},[948,1479,1480],{"class":979},"'Fixture customer with a refund and a subscription'",[948,1482,1458],{"class":965},[948,1484,1485,1487,1490,1492,1495,1497,1500],{"class":950,"line":1081},[948,1486,1434],{"class":965},[948,1488,1489],{"class":979},"'linus'",[948,1491,1440],{"class":965},[948,1493,1494],{"class":979},"'Linus Torvalds'",[948,1496,1452],{"class":965},[948,1498,1499],{"class":979},"'Fixture customer with a draft invoice'",[948,1501,1458],{"class":965},[948,1503,1504],{"class":950,"line":1095},[948,1505,1506],{"class":965},"];\n",[948,1508,1509],{"class":950,"line":1120},[948,1510,1006],{"emptyLinePlaceholder":1005},[948,1512,1513,1515,1518,1520],{"class":950,"line":1195},[948,1514,1399],{"class":961},[948,1516,1517],{"class":1024}," products",[948,1519,1028],{"class":961},[948,1521,1429],{"class":965},[948,1523,1524,1527,1530,1532,1535,1537,1540],{"class":950,"line":1240},[948,1525,1526],{"class":965},"    { id: ",[948,1528,1529],{"class":979},"'prod_msfx_starter'",[948,1531,1440],{"class":965},[948,1533,1534],{"class":979},"'Fixture Starter'",[948,1536,1452],{"class":965},[948,1538,1539],{"class":979},"'Starter plan'",[948,1541,1542],{"class":965},", prices: [\n",[948,1544,1545,1548,1551,1554,1557,1560,1563],{"class":950,"line":1246},[948,1546,1547],{"class":965},"        { nickname: ",[948,1549,1550],{"class":979},"'msfx_starter_monthly'",[948,1552,1553],{"class":965},", unit_amount: ",[948,1555,1556],{"class":979},"'900'",[948,1558,1559],{"class":965},", interval: ",[948,1561,1562],{"class":979},"'month'",[948,1564,1458],{"class":965},[948,1566,1567],{"class":950,"line":1298},[948,1568,1569],{"class":965},"    ] },\n",[948,1571,1572,1574,1577,1579,1582,1584,1587],{"class":950,"line":1308},[948,1573,1526],{"class":965},[948,1575,1576],{"class":979},"'prod_msfx_pro'",[948,1578,1440],{"class":965},[948,1580,1581],{"class":979},"'Fixture Pro'",[948,1583,1452],{"class":965},[948,1585,1586],{"class":979},"'Pro plan'",[948,1588,1542],{"class":965},[948,1590,1592,1594,1597,1599,1602,1604,1606],{"class":950,"line":1591},19,[948,1593,1547],{"class":965},[948,1595,1596],{"class":979},"'msfx_pro_monthly'",[948,1598,1553],{"class":965},[948,1600,1601],{"class":979},"'2900'",[948,1603,1559],{"class":965},[948,1605,1562],{"class":979},[948,1607,1458],{"class":965},[948,1609,1611,1613,1616,1618,1621],{"class":950,"line":1610},20,[948,1612,1547],{"class":965},[948,1614,1615],{"class":979},"'msfx_pro_setup'",[948,1617,1553],{"class":965},[948,1619,1620],{"class":979},"'5000'",[948,1622,1458],{"class":965},[948,1624,1626],{"class":950,"line":1625},21,[948,1627,1569],{"class":965},[948,1629,1631],{"class":950,"line":1630},22,[948,1632,1633],{"class":954},"    \u002F\u002F A product with a price can't be deleted: the smokes use this one for that refusal.\n",[948,1635,1637,1639,1642,1644,1647,1649,1652],{"class":950,"line":1636},23,[948,1638,1526],{"class":965},[948,1640,1641],{"class":979},"'prod_msfx_locked'",[948,1643,1440],{"class":965},[948,1645,1646],{"class":979},"'Fixture Locked'",[948,1648,1452],{"class":965},[948,1650,1651],{"class":979},"'Cannot be deleted'",[948,1653,1542],{"class":965},[948,1655,1657,1659,1662,1664,1667],{"class":950,"line":1656},24,[948,1658,1547],{"class":965},[948,1660,1661],{"class":979},"'msfx_locked'",[948,1663,1553],{"class":965},[948,1665,1666],{"class":979},"'100'",[948,1668,1458],{"class":965},[948,1670,1672],{"class":950,"line":1671},25,[948,1673,1569],{"class":965},[948,1675,1677],{"class":950,"line":1676},26,[948,1678,1506],{"class":965},[948,1680,1682],{"class":950,"line":1681},27,[948,1683,1006],{"emptyLinePlaceholder":1005},[948,1685,1687,1689,1692,1694],{"class":950,"line":1686},28,[948,1688,1399],{"class":961},[948,1690,1691],{"class":1024}," created",[948,1693,1028],{"class":961},[948,1695,1696],{"class":965}," [];\n",[948,1698,1700],{"class":950,"line":1699},29,[948,1701,1006],{"emptyLinePlaceholder":1005},[948,1703,1705,1708,1710,1712,1714,1716,1718,1721,1724,1726,1729,1731],{"class":950,"line":1704},30,[948,1706,1707],{"class":961},"for",[948,1709,1153],{"class":965},[948,1711,1399],{"class":961},[948,1713,966],{"class":965},[948,1715,1203],{"class":1024},[948,1717,1181],{"class":965},[948,1719,1720],{"class":961},"...",[948,1722,1723],{"class":1024},"fields",[948,1725,973],{"class":965},[948,1727,1728],{"class":961},"of",[948,1730,1424],{"class":969},[948,1732,1733],{"class":965},") {\n",[948,1735,1737,1739,1742,1744,1747,1749,1752,1754,1757],{"class":950,"line":1736},31,[948,1738,1098],{"class":961},[948,1740,1741],{"class":1024}," email",[948,1743,1028],{"class":961},[948,1745,1746],{"class":979}," `${",[948,1748,1203],{"class":969},[948,1750,1751],{"class":979},"}@${",[948,1753,1366],{"class":1024},[948,1755,1756],{"class":979},"}`",[948,1758,983],{"class":965},[948,1760,1762,1764,1767,1770,1773,1776,1779,1781,1783,1786,1788,1791,1794,1797,1800,1803],{"class":950,"line":1761},32,[948,1763,1098],{"class":961},[948,1765,1766],{"class":965}," [",[948,1768,1769],{"class":1024},"existing",[948,1771,1772],{"class":965},"] ",[948,1774,1775],{"class":961},"=",[948,1777,1778],{"class":961}," await",[948,1780,1402],{"class":969},[948,1782,476],{"class":965},[948,1784,1785],{"class":1024},"list",[948,1787,1176],{"class":965},[948,1789,1790],{"class":979},"'customers'",[948,1792,1793],{"class":965},", { ",[948,1795,1796],{"class":969},"email",[948,1798,1799],{"class":965},", limit: ",[948,1801,1802],{"class":979},"'1'",[948,1804,1805],{"class":965}," });\n",[948,1807,1809,1811,1813,1815,1818,1820,1822,1825],{"class":950,"line":1808},33,[948,1810,1198],{"class":961},[948,1812,1153],{"class":965},[948,1814,1769],{"class":969},[948,1816,1817],{"class":961}," !==",[948,1819,1162],{"class":1024},[948,1821,1220],{"class":965},[948,1823,1824],{"class":961},"continue",[948,1826,983],{"class":965},[948,1828,1830],{"class":950,"line":1829},34,[948,1831,1006],{"emptyLinePlaceholder":1005},[948,1833,1835,1838,1840,1842,1845,1847,1849,1851,1853,1855,1857,1859,1861,1864,1867,1869,1871,1874,1876,1878],{"class":950,"line":1834},35,[948,1836,1837],{"class":961},"    await",[948,1839,1402],{"class":969},[948,1841,476],{"class":965},[948,1843,1844],{"class":1024},"post",[948,1846,1176],{"class":965},[948,1848,1790],{"class":979},[948,1850,1793],{"class":965},[948,1852,1720],{"class":961},[948,1854,1723],{"class":969},[948,1856,1181],{"class":965},[948,1858,1796],{"class":969},[948,1860,1181],{"class":965},[948,1862,1863],{"class":979},"'metadata[fixture]'",[948,1865,1866],{"class":965},": ",[948,1868,1371],{"class":1024},[948,1870,1181],{"class":965},[948,1872,1873],{"class":979},"'metadata[fixture_key]'",[948,1875,1866],{"class":965},[948,1877,1203],{"class":969},[948,1879,1805],{"class":965},[948,1881,1883,1886,1888,1891,1893,1895],{"class":950,"line":1882},36,[948,1884,1885],{"class":969},"    created",[948,1887,476],{"class":965},[948,1889,1890],{"class":1024},"push",[948,1892,1176],{"class":965},[948,1894,1796],{"class":969},[948,1896,1237],{"class":965},[948,1898,1900],{"class":950,"line":1899},37,[948,1901,1311],{"class":965},[948,1903,1905],{"class":950,"line":1904},38,[948,1906,1006],{"emptyLinePlaceholder":1005},[948,1908,1910,1912,1914,1916,1918,1921,1923,1926,1928,1930,1932,1934,1936,1938],{"class":950,"line":1909},39,[948,1911,1707],{"class":961},[948,1913,1153],{"class":965},[948,1915,1399],{"class":961},[948,1917,966],{"class":965},[948,1919,1920],{"class":1024},"id",[948,1922,1181],{"class":965},[948,1924,1925],{"class":1024},"prices",[948,1927,1181],{"class":965},[948,1929,1720],{"class":961},[948,1931,1723],{"class":1024},[948,1933,973],{"class":965},[948,1935,1728],{"class":961},[948,1937,1517],{"class":969},[948,1939,1733],{"class":965},[948,1941,1943,1945,1948,1950,1952,1954,1956,1959,1961,1964,1966,1968,1970,1973,1976,1979,1981,1984],{"class":950,"line":1942},40,[948,1944,1098],{"class":961},[948,1946,1947],{"class":1024}," existing",[948,1949,1028],{"class":961},[948,1951,1778],{"class":961},[948,1953,1402],{"class":969},[948,1955,476],{"class":965},[948,1957,1958],{"class":1024},"get",[948,1960,1176],{"class":965},[948,1962,1963],{"class":979},"`products\u002F${",[948,1965,1920],{"class":969},[948,1967,1756],{"class":979},[948,1969,1187],{"class":965},[948,1971,1972],{"class":1024},"catch",[948,1974,1975],{"class":965},"((",[948,1977,1978],{"class":961},"error",[948,1980,1220],{"class":965},[948,1982,1983],{"class":961},"=>",[948,1985,1986],{"class":965}," {\n",[948,1988,1990,1993,1995,1997,1999,2002,2004,2007,2009,2012,2014],{"class":950,"line":1989},41,[948,1991,1992],{"class":961},"        if",[948,1994,1153],{"class":965},[948,1996,1978],{"class":969},[948,1998,476],{"class":965},[948,2000,2001],{"class":969},"status",[948,2003,1159],{"class":961},[948,2005,2006],{"class":1024}," 404",[948,2008,1220],{"class":965},[948,2010,2011],{"class":961},"return",[948,2013,1162],{"class":1024},[948,2015,983],{"class":965},[948,2017,2019,2022,2025],{"class":950,"line":2018},42,[948,2020,2021],{"class":961},"        throw",[948,2023,2024],{"class":969}," error",[948,2026,983],{"class":965},[948,2028,2030],{"class":950,"line":2029},43,[948,2031,2032],{"class":965},"    });\n",[948,2034,2036],{"class":950,"line":2035},44,[948,2037,1006],{"emptyLinePlaceholder":1005},[948,2039,2041,2043,2045,2047,2049,2051],{"class":950,"line":2040},45,[948,2042,1198],{"class":961},[948,2044,1153],{"class":965},[948,2046,1769],{"class":969},[948,2048,1159],{"class":961},[948,2050,1162],{"class":1024},[948,2052,1733],{"class":965},[948,2054,2056,2059,2061,2063,2065,2067,2070,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096],{"class":950,"line":2055},46,[948,2057,2058],{"class":961},"        await",[948,2060,1402],{"class":969},[948,2062,476],{"class":965},[948,2064,1844],{"class":1024},[948,2066,1176],{"class":965},[948,2068,2069],{"class":979},"'products'",[948,2071,1793],{"class":965},[948,2073,1920],{"class":969},[948,2075,1181],{"class":965},[948,2077,1720],{"class":961},[948,2079,1723],{"class":969},[948,2081,1181],{"class":965},[948,2083,1863],{"class":979},[948,2085,1866],{"class":965},[948,2087,1371],{"class":1024},[948,2089,1181],{"class":965},[948,2091,1873],{"class":979},[948,2093,1866],{"class":965},[948,2095,1920],{"class":969},[948,2097,1805],{"class":965},[948,2099,2101,2104,2106,2108,2110,2112],{"class":950,"line":2100},47,[948,2102,2103],{"class":969},"        created",[948,2105,476],{"class":965},[948,2107,1890],{"class":1024},[948,2109,1176],{"class":965},[948,2111,1920],{"class":969},[948,2113,1237],{"class":965},[948,2115,2117],{"class":950,"line":2116},48,[948,2118,2119],{"class":965},"    }\n",[948,2121,2123],{"class":950,"line":2122},49,[948,2124,1006],{"emptyLinePlaceholder":1005},[948,2126,2128,2130,2133,2135,2137,2139,2141,2143,2145,2148,2151,2153,2155,2157],{"class":950,"line":2127},50,[948,2129,1098],{"class":961},[948,2131,2132],{"class":1024}," current",[948,2134,1028],{"class":961},[948,2136,1778],{"class":961},[948,2138,1402],{"class":969},[948,2140,476],{"class":965},[948,2142,1785],{"class":1024},[948,2144,1176],{"class":965},[948,2146,2147],{"class":979},"'prices'",[948,2149,2150],{"class":965},", { product: ",[948,2152,1920],{"class":969},[948,2154,1799],{"class":965},[948,2156,1666],{"class":979},[948,2158,1805],{"class":965},[948,2160,2162],{"class":950,"line":2161},51,[948,2163,1006],{"emptyLinePlaceholder":1005},[948,2165,2167,2170,2172,2174,2176,2179,2181,2184,2186,2189,2191,2193,2196],{"class":950,"line":2166},52,[948,2168,2169],{"class":961},"    for",[948,2171,1153],{"class":965},[948,2173,1399],{"class":961},[948,2175,966],{"class":965},[948,2177,2178],{"class":1024},"nickname",[948,2180,1181],{"class":965},[948,2182,2183],{"class":1024},"unit_amount",[948,2185,1181],{"class":965},[948,2187,2188],{"class":1024},"interval",[948,2190,973],{"class":965},[948,2192,1728],{"class":961},[948,2194,2195],{"class":969}," prices",[948,2197,1733],{"class":965},[948,2199,2201,2203,2205,2208,2210,2213,2215,2218,2220,2222,2225,2227,2229,2231,2234,2236,2238],{"class":950,"line":2200},53,[948,2202,1992],{"class":961},[948,2204,1153],{"class":965},[948,2206,2207],{"class":969},"current",[948,2209,476],{"class":965},[948,2211,2212],{"class":1024},"some",[948,2214,1975],{"class":965},[948,2216,2217],{"class":961},"price",[948,2219,1220],{"class":965},[948,2221,1983],{"class":961},[948,2223,2224],{"class":969}," price",[948,2226,476],{"class":965},[948,2228,2178],{"class":969},[948,2230,1159],{"class":961},[948,2232,2233],{"class":969}," nickname",[948,2235,1282],{"class":965},[948,2237,1824],{"class":961},[948,2239,983],{"class":965},[948,2241,2243],{"class":950,"line":2242},54,[948,2244,1006],{"emptyLinePlaceholder":1005},[948,2246,2248,2250,2252,2254,2256,2258,2260],{"class":950,"line":2247},55,[948,2249,2058],{"class":961},[948,2251,1402],{"class":969},[948,2253,476],{"class":965},[948,2255,1844],{"class":1024},[948,2257,1176],{"class":965},[948,2259,2147],{"class":979},[948,2261,2262],{"class":965},", {\n",[948,2264,2266,2269,2271],{"class":950,"line":2265},56,[948,2267,2268],{"class":965},"            product: ",[948,2270,1920],{"class":969},[948,2272,2273],{"class":965},",\n",[948,2275,2277,2280,2283],{"class":950,"line":2276},57,[948,2278,2279],{"class":965},"            currency: ",[948,2281,2282],{"class":979},"'eur'",[948,2284,2273],{"class":965},[948,2286,2288,2291],{"class":950,"line":2287},58,[948,2289,2290],{"class":969},"            unit_amount",[948,2292,2273],{"class":965},[948,2294,2296,2299],{"class":950,"line":2295},59,[948,2297,2298],{"class":969},"            nickname",[948,2300,2273],{"class":965},[948,2302,2304,2307,2309,2311,2313,2315,2317,2320,2323,2325,2328,2330,2332],{"class":950,"line":2303},60,[948,2305,2306],{"class":961},"            ...",[948,2308,1176],{"class":965},[948,2310,2188],{"class":969},[948,2312,1159],{"class":961},[948,2314,1162],{"class":1024},[948,2316,1165],{"class":961},[948,2318,2319],{"class":965}," {} ",[948,2321,2322],{"class":961},":",[948,2324,966],{"class":965},[948,2326,2327],{"class":979},"'recurring[interval]'",[948,2329,1866],{"class":965},[948,2331,2188],{"class":969},[948,2333,2334],{"class":965}," }),\n",[948,2336,2338,2341,2343,2345],{"class":950,"line":2337},61,[948,2339,2340],{"class":979},"            'metadata[fixture]'",[948,2342,1866],{"class":965},[948,2344,1371],{"class":1024},[948,2346,2273],{"class":965},[948,2348,2350,2353,2355,2357],{"class":950,"line":2349},62,[948,2351,2352],{"class":979},"            'metadata[fixture_key]'",[948,2354,1866],{"class":965},[948,2356,2178],{"class":969},[948,2358,2273],{"class":965},[948,2360,2362],{"class":950,"line":2361},63,[948,2363,2364],{"class":965},"        });\n",[948,2366,2368,2370,2372,2374,2376,2378],{"class":950,"line":2367},64,[948,2369,2103],{"class":969},[948,2371,476],{"class":965},[948,2373,1890],{"class":1024},[948,2375,1176],{"class":965},[948,2377,2178],{"class":969},[948,2379,1237],{"class":965},[948,2381,2383],{"class":950,"line":2382},65,[948,2384,2119],{"class":965},[948,2386,2388],{"class":950,"line":2387},66,[948,2389,1311],{"class":965},[948,2391,2393],{"class":950,"line":2392},67,[948,2394,1006],{"emptyLinePlaceholder":1005},[948,2396,2398,2401,2403,2406,2408,2411,2413,2416,2419,2421,2424,2427,2430,2432,2435,2438,2441,2444,2447],{"class":950,"line":2397},68,[948,2399,2400],{"class":969},"process",[948,2402,476],{"class":965},[948,2404,2405],{"class":969},"stdout",[948,2407,476],{"class":965},[948,2409,2410],{"class":1024},"write",[948,2412,1176],{"class":965},[948,2414,2415],{"class":979},"`${",[948,2417,2418],{"class":1024},"JSON",[948,2420,476],{"class":979},[948,2422,2423],{"class":1024},"stringify",[948,2425,2426],{"class":979},"({ ok: ",[948,2428,2429],{"class":1024},"true",[948,2431,1181],{"class":979},[948,2433,2434],{"class":969},"created",[948,2436,2437],{"class":979}," })",[948,2439,2440],{"class":979},"}",[948,2442,2443],{"class":1024},"\\n",[948,2445,2446],{"class":979},"`",[948,2448,1237],{"class":965},[2450,2451,2453],"h3",{"id":2452},"emulators","Emulators",[458,2455,2456],{},"An emulator is a state fixture, not a model of the real API. Design against the vendor's documentation and live probes, and use the emulator for state only.",[884,2458,2459,2465,2471],{},[887,2460,2461,2464],{},[462,2462,2463],{},"Authentication:"," an emulator that accepts any token can't test a rejected credential.",[887,2466,2467,2470],{},[462,2468,2469],{},"Parameters:"," an emulator that doesn't implement a parameter can ignore it and answer with unfiltered items. A pushdown bug then returns the same items as correct code.",[887,2472,2473,2476],{},[462,2474,2475],{},"Paging and shapes:"," paging, timestamps, response shapes, and rate-limit statuses can differ from the real API.",[458,2478,2479,2480,2484],{},"Cover what the emulator can't show with ",[467,2481,2483],{"href":2482},"#assert-what-goes-on-the-wire","wire assertions"," and a stand-in.",[454,2486,2488],{"id":2487},"_1-lint-typecheck-and-run-unit-tests","1. Lint, Typecheck, and Run Unit Tests",[458,2490,2491,2492,2494],{},"Run the linter, ",[506,2493,702],{},", and your unit tests. The build bundles without typechecking, so a type error reaches the artifact unnoticed.",[2496,2497,2499,2517,2529,2541],"code-group",{"sync":2498},"pm",[938,2500,2505],{"className":2501,"code":2502,"filename":2503,"language":2504,"meta":944,"style":944},"language-bash shiki shiki-themes monospace-light monospace-light monospace-dark","npm run lint\n","npm","bash",[506,2506,2507],{"__ignoreMap":944},[948,2508,2509,2511,2514],{"class":950,"line":951},[948,2510,2503],{"class":1024},[948,2512,2513],{"class":979}," run",[948,2515,2516],{"class":979}," lint\n",[938,2518,2521],{"className":2501,"code":2519,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm lint\n","pnpm",[506,2522,2523],{"__ignoreMap":944},[948,2524,2525,2527],{"class":950,"line":951},[948,2526,2520],{"class":1024},[948,2528,2516],{"class":979},[938,2530,2533],{"className":2501,"code":2531,"filename":2532,"language":2504,"meta":944,"style":944},"yarn lint\n","yarn",[506,2534,2535],{"__ignoreMap":944},[948,2536,2537,2539],{"class":950,"line":951},[948,2538,2532],{"class":1024},[948,2540,2516],{"class":979},[938,2542,2545],{"className":2501,"code":2543,"filename":2544,"language":2504,"meta":944,"style":944},"bun run lint\n","bun",[506,2546,2547],{"__ignoreMap":944},[948,2548,2549,2551,2553],{"class":950,"line":951},[948,2550,2544],{"class":1024},[948,2552,2513],{"class":979},[948,2554,2516],{"class":979},[2496,2556,2557,2571,2582,2593],{"sync":2498},[938,2558,2560],{"className":2501,"code":2559,"filename":2503,"language":2504,"meta":944,"style":944},"npm run typecheck\n",[506,2561,2562],{"__ignoreMap":944},[948,2563,2564,2566,2568],{"class":950,"line":951},[948,2565,2503],{"class":1024},[948,2567,2513],{"class":979},[948,2569,2570],{"class":979}," typecheck\n",[938,2572,2574],{"className":2501,"code":2573,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm typecheck\n",[506,2575,2576],{"__ignoreMap":944},[948,2577,2578,2580],{"class":950,"line":951},[948,2579,2520],{"class":1024},[948,2581,2570],{"class":979},[938,2583,2585],{"className":2501,"code":2584,"filename":2532,"language":2504,"meta":944,"style":944},"yarn typecheck\n",[506,2586,2587],{"__ignoreMap":944},[948,2588,2589,2591],{"class":950,"line":951},[948,2590,2532],{"class":1024},[948,2592,2570],{"class":979},[938,2594,2596],{"className":2501,"code":2595,"filename":2544,"language":2504,"meta":944,"style":944},"bun run typecheck\n",[506,2597,2598],{"__ignoreMap":944},[948,2599,2600,2602,2604],{"class":950,"line":951},[948,2601,2544],{"class":1024},[948,2603,2513],{"class":979},[948,2605,2570],{"class":979},[2496,2607,2608,2622,2633,2644],{"sync":2498},[938,2609,2611],{"className":2501,"code":2610,"filename":2503,"language":2504,"meta":944,"style":944},"npm run test\n",[506,2612,2613],{"__ignoreMap":944},[948,2614,2615,2617,2619],{"class":950,"line":951},[948,2616,2503],{"class":1024},[948,2618,2513],{"class":979},[948,2620,2621],{"class":979}," test\n",[938,2623,2625],{"className":2501,"code":2624,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm test\n",[506,2626,2627],{"__ignoreMap":944},[948,2628,2629,2631],{"class":950,"line":951},[948,2630,2520],{"class":1024},[948,2632,2621],{"class":979},[938,2634,2636],{"className":2501,"code":2635,"filename":2532,"language":2504,"meta":944,"style":944},"yarn test\n",[506,2637,2638],{"__ignoreMap":944},[948,2639,2640,2642],{"class":950,"line":951},[948,2641,2532],{"class":1024},[948,2643,2621],{"class":979},[938,2645,2647],{"className":2501,"code":2646,"filename":2544,"language":2504,"meta":944,"style":944},"bun run test\n",[506,2648,2649],{"__ignoreMap":944},[948,2650,2651,2653,2655],{"class":950,"line":951},[948,2652,2544],{"class":1024},[948,2654,2513],{"class":979},[948,2656,2621],{"class":979},[458,2658,2659,2660,2662],{},"The example projects lint with ESLint's recommended rules and ",[506,2661,686],{},"'s strict and stylistic type-checked rules:",[938,2664,2667],{"className":940,"code":2665,"filename":2666,"language":943,"meta":944,"style":944},"import js from '@eslint\u002Fjs';\nimport { defineConfig } from 'eslint\u002Fconfig';\nimport globals from 'globals';\nimport tseslint from 'typescript-eslint';\n\nexport default defineConfig(\n    { ignores: ['dist\u002F'] },\n    js.configs.recommended,\n    {\n        \u002F\u002F The smoke scripts run in Node.\n        files: ['scripts\u002F**\u002F*.mjs'],\n        languageOptions: { globals: globals.node },\n    },\n    {\n        files: ['**\u002F*.ts'],\n        extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked],\n        languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname } },\n        \u002F\u002F Numbers read fine in messages; the rule still catches objects and `undefined`.\n        rules: { '@typescript-eslint\u002Frestrict-template-expressions': ['error', { allowNumber: true }] },\n    },\n);\n","eslint.config.js",[506,2668,2669,2683,2701,2715,2729,2733,2746,2757,2774,2779,2784,2795,2810,2815,2819,2828,2860,2886,2891,2913,2917],{"__ignoreMap":944},[948,2670,2671,2673,2676,2678,2681],{"class":950,"line":951},[948,2672,962],{"class":961},[948,2674,2675],{"class":969}," js",[948,2677,994],{"class":961},[948,2679,2680],{"class":979}," '@eslint\u002Fjs'",[948,2682,983],{"class":965},[948,2684,2685,2687,2689,2692,2694,2696,2699],{"class":950,"line":958},[948,2686,962],{"class":961},[948,2688,966],{"class":965},[948,2690,2691],{"class":969},"defineConfig",[948,2693,973],{"class":965},[948,2695,976],{"class":961},[948,2697,2698],{"class":979}," 'eslint\u002Fconfig'",[948,2700,983],{"class":965},[948,2702,2703,2705,2708,2710,2713],{"class":950,"line":986},[948,2704,962],{"class":961},[948,2706,2707],{"class":969}," globals",[948,2709,994],{"class":961},[948,2711,2712],{"class":979}," 'globals'",[948,2714,983],{"class":965},[948,2716,2717,2719,2722,2724,2727],{"class":950,"line":1002},[948,2718,962],{"class":961},[948,2720,2721],{"class":969}," tseslint",[948,2723,994],{"class":961},[948,2725,2726],{"class":979}," 'typescript-eslint'",[948,2728,983],{"class":965},[948,2730,2731],{"class":950,"line":1009},[948,2732,1006],{"emptyLinePlaceholder":1005},[948,2734,2735,2737,2740,2743],{"class":950,"line":1015},[948,2736,1018],{"class":961},[948,2738,2739],{"class":961}," default",[948,2741,2742],{"class":1024}," defineConfig",[948,2744,2745],{"class":965},"(\n",[948,2747,2748,2751,2754],{"class":950,"line":1036},[948,2749,2750],{"class":965},"    { ignores: [",[948,2752,2753],{"class":979},"'dist\u002F'",[948,2755,2756],{"class":965},"] },\n",[948,2758,2759,2762,2764,2767,2769,2772],{"class":950,"line":1053},[948,2760,2761],{"class":969},"    js",[948,2763,476],{"class":965},[948,2765,2766],{"class":969},"configs",[948,2768,476],{"class":965},[948,2770,2771],{"class":969},"recommended",[948,2773,2273],{"class":965},[948,2775,2776],{"class":950,"line":1070},[948,2777,2778],{"class":965},"    {\n",[948,2780,2781],{"class":950,"line":1075},[948,2782,2783],{"class":954},"        \u002F\u002F The smoke scripts run in Node.\n",[948,2785,2786,2789,2792],{"class":950,"line":1081},[948,2787,2788],{"class":965},"        files: [",[948,2790,2791],{"class":979},"'scripts\u002F**\u002F*.mjs'",[948,2793,2794],{"class":965},"],\n",[948,2796,2797,2800,2803,2805,2808],{"class":950,"line":1095},[948,2798,2799],{"class":965},"        languageOptions: { globals: ",[948,2801,2802],{"class":969},"globals",[948,2804,476],{"class":965},[948,2806,2807],{"class":969},"node",[948,2809,1458],{"class":965},[948,2811,2812],{"class":950,"line":1120},[948,2813,2814],{"class":965},"    },\n",[948,2816,2817],{"class":950,"line":1195},[948,2818,2778],{"class":965},[948,2820,2821,2823,2826],{"class":950,"line":1240},[948,2822,2788],{"class":965},[948,2824,2825],{"class":979},"'**\u002F*.ts'",[948,2827,2794],{"class":965},[948,2829,2830,2833,2836,2838,2840,2842,2845,2847,2849,2851,2853,2855,2858],{"class":950,"line":1246},[948,2831,2832],{"class":965},"        extends: [",[948,2834,2835],{"class":969},"tseslint",[948,2837,476],{"class":965},[948,2839,2766],{"class":969},[948,2841,476],{"class":965},[948,2843,2844],{"class":969},"strictTypeChecked",[948,2846,1181],{"class":965},[948,2848,2835],{"class":969},[948,2850,476],{"class":965},[948,2852,2766],{"class":969},[948,2854,476],{"class":965},[948,2856,2857],{"class":969},"stylisticTypeChecked",[948,2859,2794],{"class":965},[948,2861,2862,2865,2867,2870,2872,2874,2878,2880,2883],{"class":950,"line":1298},[948,2863,2864],{"class":965},"        languageOptions: { parserOptions: { projectService: ",[948,2866,2429],{"class":1024},[948,2868,2869],{"class":965},", tsconfigRootDir: ",[948,2871,962],{"class":961},[948,2873,476],{"class":965},[948,2875,2877],{"class":2876},"snHjA","meta",[948,2879,476],{"class":965},[948,2881,2882],{"class":969},"dirname",[948,2884,2885],{"class":965}," } },\n",[948,2887,2888],{"class":950,"line":1308},[948,2889,2890],{"class":954},"        \u002F\u002F Numbers read fine in messages; the rule still catches objects and `undefined`.\n",[948,2892,2893,2896,2899,2902,2905,2908,2910],{"class":950,"line":1591},[948,2894,2895],{"class":965},"        rules: { ",[948,2897,2898],{"class":979},"'@typescript-eslint\u002Frestrict-template-expressions'",[948,2900,2901],{"class":965},": [",[948,2903,2904],{"class":979},"'error'",[948,2906,2907],{"class":965},", { allowNumber: ",[948,2909,2429],{"class":1024},[948,2911,2912],{"class":965}," }] },\n",[948,2914,2915],{"class":950,"line":1610},[948,2916,2814],{"class":965},[948,2918,2919],{"class":950,"line":1625},[948,2920,1237],{"class":965},[458,2922,2923],{},"Unit tests import your sources, not the build, and run without the network. The Stripe connector has three test files:",[515,2925,2926,2936],{},[518,2927,2928],{},[521,2929,2930,2933],{},[524,2931,2932],{},"File",[524,2934,2935],{},"Covers",[534,2937,2938,2951,2961],{},[521,2939,2940,2945],{},[539,2941,2942],{},[506,2943,2944],{},"test\u002Fplan.test.ts",[539,2946,2947,2948,2950],{},"Which Stripe requests a filter becomes, the ",[506,2949,2434],{}," bounds for whole and fractional seconds, each refusal, and that every declared operator plans",[521,2952,2953,2958],{},[539,2954,2955],{},[506,2956,2957],{},"test\u002Ffilter.test.ts",[539,2959,2960],{},"The in-memory re-check: SQL three-valued logic, and timestamps compared below the millisecond",[521,2962,2963,2968],{},[539,2964,2965],{},[506,2966,2967],{},"test\u002Fconnector.test.ts",[539,2969,2970,2971,2973,2974,2977,2978,1181,2980,2982,2983,2986,2987,2991],{},"The handlers, with ",[506,2972,865],{}," replaced by a fake Stripe: configuration, timeouts, permission denials kept as ",[506,2975,2976],{},"cause",", bodies that aren't JSON, ",[506,2979,875],{},[506,2981,879],{},", messages that leave out keys and query strings, keyed operations, reads, writes that fail partway, and writes that answer the items they wrote, projected to ",[506,2984,2985],{},"select",", as the three declared ",[467,2988,2990],{"href":2989},"\u002Freference\u002Fextensions\u002Fmanifest#capabilities","query capabilities"," promise",[458,2993,2994,2997],{},[506,2995,2996],{},"connector.test.ts"," records every request and lets each test decide the answer. That covers what a live account can't produce on demand:",[1333,2999,3000],{},[1336,3001,3003],{"label":3002},"test\u002Fconnector.test.ts (fake Stripe)",[938,3004,3008],{"className":3005,"code":3006,"filename":2967,"language":3007,"meta":944,"style":944},"language-ts shiki shiki-themes monospace-light monospace-light monospace-dark","\u002F\u002F Network-free tests through the connector's handlers, with `fetch` replaced by a fake Stripe.\n\u002F\u002F They cover what a live account can't produce on demand: timeouts, bad bodies, rate limits, and\n\u002F\u002F a bulk write that fails partway.\nimport type { FilterSerialized, OperationSerialized, ReadManyOperationSerialized } from '@monospace\u002Fextension-kit\u002Fdata-connector';\nimport {\n    AccessDenied,\n    ConnectionFailed,\n    ForeignKeyConstraintViolation,\n    InvalidConfiguration,\n    QueryRejected,\n    RateLimited,\n} from '@monospace\u002Fextension-kit\u002Fdata-connector';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\nimport connector from '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Findex';\n\ninterface SentRequest {\n    readonly method: string;\n    \u002F** Below `\u002Fv1\u002F`. *\u002F\n    readonly path: string;\n    readonly search: URLSearchParams;\n    readonly form: URLSearchParams;\n}\n\nlet sent: SentRequest[];\nlet answer: (request: SentRequest) => Response | Promise\u003CResponse>;\n\nbeforeEach(() => {\n    sent = [];\n    answer = () => Response.json({ data: [], has_more: false });\n\n    vi.stubGlobal('fetch', async (url: URL, init: { method: string; body?: string }) => {\n        const request = { method: init.method, path: url.pathname.replace('\u002Fv1\u002F', ''), search: url.searchParams, form: new URLSearchParams(init.body) };\n        sent.push(request);\n        return await answer(request);\n    });\n\n    return () => vi.unstubAllGlobals();\n});\n\nconst API_KEY = 'sk_test_fake';\n\nasync function handlers(config: Record\u003Cstring, unknown> = {}) {\n    return await connector.setup({ config: { apiKey: API_KEY, ...config } });\n}\n\nasync function query(operation: OperationSerialized, config?: Record\u003Cstring, unknown>) {\n    const connection = await handlers(config);\n    return await connection.query({ query: operation });\n}\n\nfunction customer(id: string, created = 1_767_225_600, fields: Record\u003Cstring, unknown> = {}) {\n    return { id, object: 'customer', name: id, email: null, phone: null, description: null, balance: 0, created, ...fields };\n}\n\nfunction stripeError(status: number, error: Record\u003Cstring, string>): Response {\n    return Response.json({ error: { ...error, request_log_url: 'https:\u002F\u002Fdashboard.stripe.com\u002Facct_123\u002Ftest\u002Flogs\u002Freq_1', doc_url: 'https:\u002F\u002Fdocs' } }, { status });\n}\n","ts",[506,3009,3010,3015,3020,3025,3056,3062,3069,3076,3083,3090,3097,3104,3115,3153,3167,3171,3181,3194,3199,3210,3222,3233,3237,3241,3256,3294,3298,3309,3318,3345,3349,3404,3481,3497,3512,3516,3520,3539,3544,3548,3562,3566,3601,3630,3634,3638,3673,3692,3713,3717,3721,3766,3817,3821,3825,3864,3900],{"__ignoreMap":944},[948,3011,3012],{"class":950,"line":951},[948,3013,3014],{"class":954},"\u002F\u002F Network-free tests through the connector's handlers, with `fetch` replaced by a fake Stripe.\n",[948,3016,3017],{"class":950,"line":958},[948,3018,3019],{"class":954},"\u002F\u002F They cover what a live account can't produce on demand: timeouts, bad bodies, rate limits, and\n",[948,3021,3022],{"class":950,"line":986},[948,3023,3024],{"class":954},"\u002F\u002F a bulk write that fails partway.\n",[948,3026,3027,3029,3032,3034,3037,3039,3042,3044,3047,3049,3051,3054],{"class":950,"line":1002},[948,3028,962],{"class":961},[948,3030,3031],{"class":961}," type",[948,3033,966],{"class":965},[948,3035,3036],{"class":969},"FilterSerialized",[948,3038,1181],{"class":965},[948,3040,3041],{"class":969},"OperationSerialized",[948,3043,1181],{"class":965},[948,3045,3046],{"class":969},"ReadManyOperationSerialized",[948,3048,973],{"class":965},[948,3050,976],{"class":961},[948,3052,3053],{"class":979}," '@monospace\u002Fextension-kit\u002Fdata-connector'",[948,3055,983],{"class":965},[948,3057,3058,3060],{"class":950,"line":1009},[948,3059,962],{"class":961},[948,3061,1986],{"class":965},[948,3063,3064,3067],{"class":950,"line":1015},[948,3065,3066],{"class":969},"    AccessDenied",[948,3068,2273],{"class":965},[948,3070,3071,3074],{"class":950,"line":1036},[948,3072,3073],{"class":969},"    ConnectionFailed",[948,3075,2273],{"class":965},[948,3077,3078,3081],{"class":950,"line":1053},[948,3079,3080],{"class":969},"    ForeignKeyConstraintViolation",[948,3082,2273],{"class":965},[948,3084,3085,3088],{"class":950,"line":1070},[948,3086,3087],{"class":969},"    InvalidConfiguration",[948,3089,2273],{"class":965},[948,3091,3092,3095],{"class":950,"line":1075},[948,3093,3094],{"class":969},"    QueryRejected",[948,3096,2273],{"class":965},[948,3098,3099,3102],{"class":950,"line":1081},[948,3100,3101],{"class":969},"    RateLimited",[948,3103,2273],{"class":965},[948,3105,3106,3109,3111,3113],{"class":950,"line":1095},[948,3107,3108],{"class":965},"} ",[948,3110,976],{"class":961},[948,3112,3053],{"class":979},[948,3114,983],{"class":965},[948,3116,3117,3119,3121,3124,3126,3129,3131,3134,3136,3139,3141,3144,3146,3148,3151],{"class":950,"line":1120},[948,3118,962],{"class":961},[948,3120,966],{"class":965},[948,3122,3123],{"class":969},"beforeEach",[948,3125,1181],{"class":965},[948,3127,3128],{"class":969},"describe",[948,3130,1181],{"class":965},[948,3132,3133],{"class":969},"expect",[948,3135,1181],{"class":965},[948,3137,3138],{"class":969},"it",[948,3140,1181],{"class":965},[948,3142,3143],{"class":969},"vi",[948,3145,973],{"class":965},[948,3147,976],{"class":961},[948,3149,3150],{"class":979}," 'vitest'",[948,3152,983],{"class":965},[948,3154,3155,3157,3160,3162,3165],{"class":950,"line":1195},[948,3156,962],{"class":961},[948,3158,3159],{"class":969}," connector",[948,3161,994],{"class":961},[948,3163,3164],{"class":979}," '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Findex'",[948,3166,983],{"class":965},[948,3168,3169],{"class":950,"line":1240},[948,3170,1006],{"emptyLinePlaceholder":1005},[948,3172,3173,3176,3179],{"class":950,"line":1246},[948,3174,3175],{"class":961},"interface",[948,3177,3178],{"class":1024}," SentRequest",[948,3180,1986],{"class":965},[948,3182,3183,3186,3189,3192],{"class":950,"line":1298},[948,3184,3185],{"class":961},"    readonly",[948,3187,3188],{"class":961}," method:",[948,3190,3191],{"class":2876}," string",[948,3193,983],{"class":965},[948,3195,3196],{"class":950,"line":1308},[948,3197,3198],{"class":954},"    \u002F** Below `\u002Fv1\u002F`. *\u002F\n",[948,3200,3201,3203,3206,3208],{"class":950,"line":1591},[948,3202,3185],{"class":961},[948,3204,3205],{"class":961}," path:",[948,3207,3191],{"class":2876},[948,3209,983],{"class":965},[948,3211,3212,3214,3217,3220],{"class":950,"line":1610},[948,3213,3185],{"class":961},[948,3215,3216],{"class":961}," search:",[948,3218,3219],{"class":1024}," URLSearchParams",[948,3221,983],{"class":965},[948,3223,3224,3226,3229,3231],{"class":950,"line":1625},[948,3225,3185],{"class":961},[948,3227,3228],{"class":961}," form:",[948,3230,3219],{"class":1024},[948,3232,983],{"class":965},[948,3234,3235],{"class":950,"line":1630},[948,3236,1311],{"class":965},[948,3238,3239],{"class":950,"line":1636},[948,3240,1006],{"emptyLinePlaceholder":1005},[948,3242,3243,3246,3249,3251,3253],{"class":950,"line":1656},[948,3244,3245],{"class":961},"let",[948,3247,3248],{"class":969}," sent",[948,3250,2322],{"class":961},[948,3252,3178],{"class":1024},[948,3254,3255],{"class":965},"[];\n",[948,3257,3258,3260,3263,3265,3267,3270,3272,3274,3276,3279,3282,3285,3288,3291],{"class":950,"line":1671},[948,3259,3245],{"class":961},[948,3261,3262],{"class":1024}," answer",[948,3264,2322],{"class":961},[948,3266,1153],{"class":965},[948,3268,3269],{"class":961},"request:",[948,3271,3178],{"class":1024},[948,3273,1220],{"class":965},[948,3275,1983],{"class":961},[948,3277,3278],{"class":1024}," Response",[948,3280,3281],{"class":961}," |",[948,3283,3284],{"class":1024}," Promise",[948,3286,3287],{"class":965},"\u003C",[948,3289,3290],{"class":1024},"Response",[948,3292,3293],{"class":965},">;\n",[948,3295,3296],{"class":950,"line":1676},[948,3297,1006],{"emptyLinePlaceholder":1005},[948,3299,3300,3302,3305,3307],{"class":950,"line":1681},[948,3301,3123],{"class":1024},[948,3303,3304],{"class":965},"(() ",[948,3306,1983],{"class":961},[948,3308,1986],{"class":965},[948,3310,3311,3314,3316],{"class":950,"line":1686},[948,3312,3313],{"class":969},"    sent",[948,3315,1028],{"class":961},[948,3317,1696],{"class":965},[948,3319,3320,3323,3325,3328,3330,3332,3334,3337,3340,3343],{"class":950,"line":1699},[948,3321,3322],{"class":1024},"    answer",[948,3324,1028],{"class":961},[948,3326,3327],{"class":965}," () ",[948,3329,1983],{"class":961},[948,3331,3278],{"class":969},[948,3333,476],{"class":965},[948,3335,3336],{"class":1024},"json",[948,3338,3339],{"class":965},"({ data: [], has_more: ",[948,3341,3342],{"class":1024},"false",[948,3344,1805],{"class":965},[948,3346,3347],{"class":950,"line":1704},[948,3348,1006],{"emptyLinePlaceholder":1005},[948,3350,3351,3354,3356,3359,3361,3364,3366,3369,3371,3374,3377,3379,3382,3384,3387,3389,3392,3395,3397,3400,3402],{"class":950,"line":1736},[948,3352,3353],{"class":969},"    vi",[948,3355,476],{"class":965},[948,3357,3358],{"class":1024},"stubGlobal",[948,3360,1176],{"class":965},[948,3362,3363],{"class":979},"'fetch'",[948,3365,1181],{"class":965},[948,3367,3368],{"class":961},"async",[948,3370,1153],{"class":965},[948,3372,3373],{"class":961},"url:",[948,3375,3376],{"class":1024}," URL",[948,3378,1181],{"class":965},[948,3380,3381],{"class":961},"init:",[948,3383,966],{"class":965},[948,3385,3386],{"class":961},"method:",[948,3388,3191],{"class":2876},[948,3390,3391],{"class":965},"; ",[948,3393,3394],{"class":961},"body?:",[948,3396,3191],{"class":2876},[948,3398,3399],{"class":965}," }) ",[948,3401,1983],{"class":961},[948,3403,1986],{"class":965},[948,3405,3406,3409,3412,3414,3417,3420,3422,3425,3428,3431,3433,3436,3438,3441,3443,3446,3448,3451,3454,3456,3458,3461,3464,3467,3469,3471,3473,3475,3478],{"class":950,"line":1761},[948,3407,3408],{"class":961},"        const",[948,3410,3411],{"class":1024}," request",[948,3413,1028],{"class":961},[948,3415,3416],{"class":965}," { method: ",[948,3418,3419],{"class":969},"init",[948,3421,476],{"class":965},[948,3423,3424],{"class":969},"method",[948,3426,3427],{"class":965},", path: ",[948,3429,3430],{"class":969},"url",[948,3432,476],{"class":965},[948,3434,3435],{"class":969},"pathname",[948,3437,476],{"class":965},[948,3439,3440],{"class":1024},"replace",[948,3442,1176],{"class":965},[948,3444,3445],{"class":979},"'\u002Fv1\u002F'",[948,3447,1181],{"class":965},[948,3449,3450],{"class":979},"''",[948,3452,3453],{"class":965},"), search: ",[948,3455,3430],{"class":969},[948,3457,476],{"class":965},[948,3459,3460],{"class":969},"searchParams",[948,3462,3463],{"class":965},", form: ",[948,3465,3466],{"class":961},"new",[948,3468,3219],{"class":1024},[948,3470,1176],{"class":965},[948,3472,3419],{"class":969},[948,3474,476],{"class":965},[948,3476,3477],{"class":969},"body",[948,3479,3480],{"class":965},") };\n",[948,3482,3483,3486,3488,3490,3492,3495],{"class":950,"line":1808},[948,3484,3485],{"class":969},"        sent",[948,3487,476],{"class":965},[948,3489,1890],{"class":1024},[948,3491,1176],{"class":965},[948,3493,3494],{"class":969},"request",[948,3496,1237],{"class":965},[948,3498,3499,3502,3504,3506,3508,3510],{"class":950,"line":1829},[948,3500,3501],{"class":961},"        return",[948,3503,1778],{"class":961},[948,3505,3262],{"class":1024},[948,3507,1176],{"class":965},[948,3509,3494],{"class":969},[948,3511,1237],{"class":965},[948,3513,3514],{"class":950,"line":1834},[948,3515,2032],{"class":965},[948,3517,3518],{"class":950,"line":1882},[948,3519,1006],{"emptyLinePlaceholder":1005},[948,3521,3522,3524,3526,3528,3531,3533,3536],{"class":950,"line":1899},[948,3523,1301],{"class":961},[948,3525,3327],{"class":965},[948,3527,1983],{"class":961},[948,3529,3530],{"class":969}," vi",[948,3532,476],{"class":965},[948,3534,3535],{"class":1024},"unstubAllGlobals",[948,3537,3538],{"class":965},"();\n",[948,3540,3541],{"class":950,"line":1904},[948,3542,3543],{"class":965},"});\n",[948,3545,3546],{"class":950,"line":1909},[948,3547,1006],{"emptyLinePlaceholder":1005},[948,3549,3550,3552,3555,3557,3560],{"class":950,"line":1942},[948,3551,1399],{"class":961},[948,3553,3554],{"class":1024}," API_KEY",[948,3556,1028],{"class":961},[948,3558,3559],{"class":979}," 'sk_test_fake'",[948,3561,983],{"class":965},[948,3563,3564],{"class":950,"line":1989},[948,3565,1006],{"emptyLinePlaceholder":1005},[948,3567,3568,3570,3572,3575,3577,3580,3583,3585,3588,3590,3593,3596,3598],{"class":950,"line":2018},[948,3569,3368],{"class":961},[948,3571,1086],{"class":961},[948,3573,3574],{"class":1024}," handlers",[948,3576,1176],{"class":965},[948,3578,3579],{"class":961},"config:",[948,3581,3582],{"class":1024}," Record",[948,3584,3287],{"class":965},[948,3586,3587],{"class":2876},"string",[948,3589,1181],{"class":965},[948,3591,3592],{"class":2876},"unknown",[948,3594,3595],{"class":965},"> ",[948,3597,1775],{"class":961},[948,3599,3600],{"class":965}," {}) {\n",[948,3602,3603,3605,3607,3609,3611,3614,3617,3620,3622,3624,3627],{"class":950,"line":2029},[948,3604,1301],{"class":961},[948,3606,1778],{"class":961},[948,3608,3159],{"class":969},[948,3610,476],{"class":965},[948,3612,3613],{"class":1024},"setup",[948,3615,3616],{"class":965},"({ config: { apiKey: ",[948,3618,3619],{"class":1024},"API_KEY",[948,3621,1181],{"class":965},[948,3623,1720],{"class":961},[948,3625,3626],{"class":969},"config",[948,3628,3629],{"class":965}," } });\n",[948,3631,3632],{"class":950,"line":2035},[948,3633,1311],{"class":965},[948,3635,3636],{"class":950,"line":2040},[948,3637,1006],{"emptyLinePlaceholder":1005},[948,3639,3640,3642,3644,3647,3649,3652,3655,3657,3660,3662,3664,3666,3668,3670],{"class":950,"line":2055},[948,3641,3368],{"class":961},[948,3643,1086],{"class":961},[948,3645,3646],{"class":1024}," query",[948,3648,1176],{"class":965},[948,3650,3651],{"class":961},"operation:",[948,3653,3654],{"class":1024}," OperationSerialized",[948,3656,1181],{"class":965},[948,3658,3659],{"class":961},"config?:",[948,3661,3582],{"class":1024},[948,3663,3287],{"class":965},[948,3665,3587],{"class":2876},[948,3667,1181],{"class":965},[948,3669,3592],{"class":2876},[948,3671,3672],{"class":965},">) {\n",[948,3674,3675,3677,3680,3682,3684,3686,3688,3690],{"class":950,"line":2100},[948,3676,1098],{"class":961},[948,3678,3679],{"class":1024}," connection",[948,3681,1028],{"class":961},[948,3683,1778],{"class":961},[948,3685,3574],{"class":1024},[948,3687,1176],{"class":965},[948,3689,3626],{"class":969},[948,3691,1237],{"class":965},[948,3693,3694,3696,3698,3700,3702,3705,3708,3711],{"class":950,"line":2116},[948,3695,1301],{"class":961},[948,3697,1778],{"class":961},[948,3699,3679],{"class":969},[948,3701,476],{"class":965},[948,3703,3704],{"class":1024},"query",[948,3706,3707],{"class":965},"({ query: ",[948,3709,3710],{"class":969},"operation",[948,3712,1805],{"class":965},[948,3714,3715],{"class":950,"line":2122},[948,3716,1311],{"class":965},[948,3718,3719],{"class":950,"line":2127},[948,3720,1006],{"emptyLinePlaceholder":1005},[948,3722,3723,3726,3729,3731,3734,3736,3738,3740,3742,3745,3747,3750,3752,3754,3756,3758,3760,3762,3764],{"class":950,"line":2161},[948,3724,3725],{"class":961},"function",[948,3727,3728],{"class":1024}," customer",[948,3730,1176],{"class":965},[948,3732,3733],{"class":961},"id:",[948,3735,3191],{"class":2876},[948,3737,1181],{"class":965},[948,3739,2434],{"class":961},[948,3741,1028],{"class":961},[948,3743,3744],{"class":1024}," 1_767_225_600",[948,3746,1181],{"class":965},[948,3748,3749],{"class":961},"fields:",[948,3751,3582],{"class":1024},[948,3753,3287],{"class":965},[948,3755,3587],{"class":2876},[948,3757,1181],{"class":965},[948,3759,3592],{"class":2876},[948,3761,3595],{"class":965},[948,3763,1775],{"class":961},[948,3765,3600],{"class":965},[948,3767,3768,3770,3772,3774,3777,3780,3782,3784,3787,3790,3792,3794,3796,3798,3801,3804,3806,3808,3810,3812,3814],{"class":950,"line":2166},[948,3769,1301],{"class":961},[948,3771,966],{"class":965},[948,3773,1920],{"class":969},[948,3775,3776],{"class":965},", object: ",[948,3778,3779],{"class":979},"'customer'",[948,3781,1440],{"class":965},[948,3783,1920],{"class":969},[948,3785,3786],{"class":965},", email: ",[948,3788,3789],{"class":1024},"null",[948,3791,1446],{"class":965},[948,3793,3789],{"class":1024},[948,3795,1452],{"class":965},[948,3797,3789],{"class":1024},[948,3799,3800],{"class":965},", balance: ",[948,3802,3803],{"class":1024},"0",[948,3805,1181],{"class":965},[948,3807,2434],{"class":969},[948,3809,1181],{"class":965},[948,3811,1720],{"class":961},[948,3813,1723],{"class":969},[948,3815,3816],{"class":965}," };\n",[948,3818,3819],{"class":950,"line":2200},[948,3820,1311],{"class":965},[948,3822,3823],{"class":950,"line":2242},[948,3824,1006],{"emptyLinePlaceholder":1005},[948,3826,3827,3829,3832,3834,3837,3840,3842,3845,3847,3849,3851,3853,3855,3858,3860,3862],{"class":950,"line":2247},[948,3828,3725],{"class":961},[948,3830,3831],{"class":1024}," stripeError",[948,3833,1176],{"class":965},[948,3835,3836],{"class":961},"status:",[948,3838,3839],{"class":2876}," number",[948,3841,1181],{"class":965},[948,3843,3844],{"class":961},"error:",[948,3846,3582],{"class":1024},[948,3848,3287],{"class":965},[948,3850,3587],{"class":2876},[948,3852,1181],{"class":965},[948,3854,3587],{"class":2876},[948,3856,3857],{"class":965},">)",[948,3859,2322],{"class":961},[948,3861,3278],{"class":1024},[948,3863,1986],{"class":965},[948,3865,3866,3868,3870,3872,3874,3877,3879,3881,3884,3887,3890,3893,3896,3898],{"class":950,"line":2265},[948,3867,1301],{"class":961},[948,3869,3278],{"class":969},[948,3871,476],{"class":965},[948,3873,3336],{"class":1024},[948,3875,3876],{"class":965},"({ error: { ",[948,3878,1720],{"class":961},[948,3880,1978],{"class":969},[948,3882,3883],{"class":965},", request_log_url: ",[948,3885,3886],{"class":979},"'https:\u002F\u002Fdashboard.stripe.com\u002Facct_123\u002Ftest\u002Flogs\u002Freq_1'",[948,3888,3889],{"class":965},", doc_url: ",[948,3891,3892],{"class":979},"'https:\u002F\u002Fdocs'",[948,3894,3895],{"class":965}," } }, { ",[948,3897,2001],{"class":969},[948,3899,1805],{"class":965},[948,3901,3902],{"class":950,"line":2276},[948,3903,1311],{"class":965},[458,3905,3906],{},"A test can then hold some writes in flight and fail another, to check that a bulk delete stops starting new writes after the first failure:",[938,3908,3910],{"className":3005,"code":3909,"filename":2967,"language":3007,"meta":944,"style":944},"    it('stops a bulk delete after the first failure, lets the writes in flight finish, and says how far it got', async () => {\n        const ids = ['cus_1', 'cus_2', 'cus_3', 'cus_4', 'cus_5', 'cus_6'];\n        let release: () => void = () => undefined;\n        const inFlight = new Promise\u003Cvoid>((resolve) => {\n            release = resolve;\n        });\n\n        answer = async (request) => {\n            \u002F\u002F Newest first follows the ids' order, so `cus_1` is deleted first.\n            if (request.method === 'GET') return Response.json(customer(request.path.split('\u002F')[1] ?? '', 100 - ids.indexOf(request.path.split('\u002F')[1] ?? '')));\n            if (request.path === 'customers\u002Fcus_1') {\n                setTimeout(release, 10);\n                return stripeError(400, { type: 'invalid_request_error', message: 'Stripe refused' });\n            }\n\n            await inFlight;\n            return Response.json({ id: request.path, deleted: true });\n        };\n\n        const filter = { in: { field: 'id' }, values: ids } as const;\n        const error = await failure(query({ op: 'deleteMany', collection: 'customers', select: ['id'], filter }));\n        expect(error).toBeInstanceOf(QueryRejected);\n        expect(error).toHaveProperty('message', 'Deleted 3 of 6 customers before this error: Stripe refused');\n        expect(sent.filter((request) => request.method === 'DELETE').map((request) => request.path)).toEqual(['customers\u002Fcus_1', 'customers\u002Fcus_2', 'customers\u002Fcus_3', 'customers\u002Fcus_4']);\n    });\n",[506,3911,3912,3932,3973,4000,4030,4042,4046,4050,4070,4075,4183,4202,4219,4245,4250,4254,4263,4290,4295,4299,4329,4371,4392,4417,4498],{"__ignoreMap":944},[948,3913,3914,3917,3919,3922,3924,3926,3928,3930],{"class":950,"line":951},[948,3915,3916],{"class":1024},"    it",[948,3918,1176],{"class":965},[948,3920,3921],{"class":979},"'stops a bulk delete after the first failure, lets the writes in flight finish, and says how far it got'",[948,3923,1181],{"class":965},[948,3925,3368],{"class":961},[948,3927,3327],{"class":965},[948,3929,1983],{"class":961},[948,3931,1986],{"class":965},[948,3933,3934,3936,3939,3941,3943,3946,3948,3951,3953,3956,3958,3961,3963,3966,3968,3971],{"class":950,"line":958},[948,3935,3408],{"class":961},[948,3937,3938],{"class":1024}," ids",[948,3940,1028],{"class":961},[948,3942,1766],{"class":965},[948,3944,3945],{"class":979},"'cus_1'",[948,3947,1181],{"class":965},[948,3949,3950],{"class":979},"'cus_2'",[948,3952,1181],{"class":965},[948,3954,3955],{"class":979},"'cus_3'",[948,3957,1181],{"class":965},[948,3959,3960],{"class":979},"'cus_4'",[948,3962,1181],{"class":965},[948,3964,3965],{"class":979},"'cus_5'",[948,3967,1181],{"class":965},[948,3969,3970],{"class":979},"'cus_6'",[948,3972,1506],{"class":965},[948,3974,3975,3978,3981,3983,3985,3987,3990,3992,3994,3996,3998],{"class":950,"line":986},[948,3976,3977],{"class":961},"        let",[948,3979,3980],{"class":1024}," release",[948,3982,2322],{"class":961},[948,3984,3327],{"class":965},[948,3986,1983],{"class":961},[948,3988,3989],{"class":2876}," void",[948,3991,1028],{"class":961},[948,3993,3327],{"class":965},[948,3995,1983],{"class":961},[948,3997,1162],{"class":1024},[948,3999,983],{"class":965},[948,4001,4002,4004,4007,4009,4011,4013,4015,4018,4021,4024,4026,4028],{"class":950,"line":1002},[948,4003,3408],{"class":961},[948,4005,4006],{"class":1024}," inFlight",[948,4008,1028],{"class":961},[948,4010,1226],{"class":961},[948,4012,3284],{"class":2876},[948,4014,3287],{"class":965},[948,4016,4017],{"class":2876},"void",[948,4019,4020],{"class":965},">((",[948,4022,4023],{"class":961},"resolve",[948,4025,1220],{"class":965},[948,4027,1983],{"class":961},[948,4029,1986],{"class":965},[948,4031,4032,4035,4037,4040],{"class":950,"line":1009},[948,4033,4034],{"class":969},"            release",[948,4036,1028],{"class":961},[948,4038,4039],{"class":969}," resolve",[948,4041,983],{"class":965},[948,4043,4044],{"class":950,"line":1015},[948,4045,2364],{"class":965},[948,4047,4048],{"class":950,"line":1036},[948,4049,1006],{"emptyLinePlaceholder":1005},[948,4051,4052,4055,4057,4060,4062,4064,4066,4068],{"class":950,"line":1053},[948,4053,4054],{"class":1024},"        answer",[948,4056,1028],{"class":961},[948,4058,4059],{"class":961}," async",[948,4061,1153],{"class":965},[948,4063,3494],{"class":961},[948,4065,1220],{"class":965},[948,4067,1983],{"class":961},[948,4069,1986],{"class":965},[948,4071,4072],{"class":950,"line":1070},[948,4073,4074],{"class":954},"            \u002F\u002F Newest first follows the ids' order, so `cus_1` is deleted first.\n",[948,4076,4077,4080,4082,4084,4086,4088,4090,4093,4095,4097,4099,4101,4103,4105,4108,4110,4112,4114,4117,4119,4122,4124,4127,4130,4133,4135,4137,4139,4141,4144,4147,4149,4151,4154,4156,4158,4160,4162,4164,4166,4168,4170,4172,4174,4176,4178,4180],{"class":950,"line":1075},[948,4078,4079],{"class":961},"            if",[948,4081,1153],{"class":965},[948,4083,3494],{"class":969},[948,4085,476],{"class":965},[948,4087,3424],{"class":969},[948,4089,1159],{"class":961},[948,4091,4092],{"class":979}," 'GET'",[948,4094,1220],{"class":965},[948,4096,2011],{"class":961},[948,4098,3278],{"class":969},[948,4100,476],{"class":965},[948,4102,3336],{"class":1024},[948,4104,1176],{"class":965},[948,4106,4107],{"class":1024},"customer",[948,4109,1176],{"class":965},[948,4111,3494],{"class":969},[948,4113,476],{"class":965},[948,4115,4116],{"class":969},"path",[948,4118,476],{"class":965},[948,4120,4121],{"class":1024},"split",[948,4123,1176],{"class":965},[948,4125,4126],{"class":979},"'\u002F'",[948,4128,4129],{"class":965},")[",[948,4131,4132],{"class":1024},"1",[948,4134,1772],{"class":965},[948,4136,1150],{"class":961},[948,4138,1217],{"class":979},[948,4140,1181],{"class":965},[948,4142,4143],{"class":1024},"100",[948,4145,4146],{"class":961}," -",[948,4148,3938],{"class":969},[948,4150,476],{"class":965},[948,4152,4153],{"class":1024},"indexOf",[948,4155,1176],{"class":965},[948,4157,3494],{"class":969},[948,4159,476],{"class":965},[948,4161,4116],{"class":969},[948,4163,476],{"class":965},[948,4165,4121],{"class":1024},[948,4167,1176],{"class":965},[948,4169,4126],{"class":979},[948,4171,4129],{"class":965},[948,4173,4132],{"class":1024},[948,4175,1772],{"class":965},[948,4177,1150],{"class":961},[948,4179,1217],{"class":979},[948,4181,4182],{"class":965},")));\n",[948,4184,4185,4187,4189,4191,4193,4195,4197,4200],{"class":950,"line":1081},[948,4186,4079],{"class":961},[948,4188,1153],{"class":965},[948,4190,3494],{"class":969},[948,4192,476],{"class":965},[948,4194,4116],{"class":969},[948,4196,1159],{"class":961},[948,4198,4199],{"class":979}," 'customers\u002Fcus_1'",[948,4201,1733],{"class":965},[948,4203,4204,4207,4209,4212,4214,4217],{"class":950,"line":1095},[948,4205,4206],{"class":1024},"                setTimeout",[948,4208,1176],{"class":965},[948,4210,4211],{"class":969},"release",[948,4213,1181],{"class":965},[948,4215,4216],{"class":1024},"10",[948,4218,1237],{"class":965},[948,4220,4221,4224,4226,4228,4231,4234,4237,4240,4243],{"class":950,"line":1120},[948,4222,4223],{"class":961},"                return",[948,4225,3831],{"class":1024},[948,4227,1176],{"class":965},[948,4229,4230],{"class":1024},"400",[948,4232,4233],{"class":965},", { type: ",[948,4235,4236],{"class":979},"'invalid_request_error'",[948,4238,4239],{"class":965},", message: ",[948,4241,4242],{"class":979},"'Stripe refused'",[948,4244,1805],{"class":965},[948,4246,4247],{"class":950,"line":1195},[948,4248,4249],{"class":965},"            }\n",[948,4251,4252],{"class":950,"line":1240},[948,4253,1006],{"emptyLinePlaceholder":1005},[948,4255,4256,4259,4261],{"class":950,"line":1246},[948,4257,4258],{"class":961},"            await",[948,4260,4006],{"class":969},[948,4262,983],{"class":965},[948,4264,4265,4268,4270,4272,4274,4277,4279,4281,4283,4286,4288],{"class":950,"line":1298},[948,4266,4267],{"class":961},"            return",[948,4269,3278],{"class":969},[948,4271,476],{"class":965},[948,4273,3336],{"class":1024},[948,4275,4276],{"class":965},"({ id: ",[948,4278,3494],{"class":969},[948,4280,476],{"class":965},[948,4282,4116],{"class":969},[948,4284,4285],{"class":965},", deleted: ",[948,4287,2429],{"class":1024},[948,4289,1805],{"class":965},[948,4291,4292],{"class":950,"line":1308},[948,4293,4294],{"class":965},"        };\n",[948,4296,4297],{"class":950,"line":1591},[948,4298,1006],{"emptyLinePlaceholder":1005},[948,4300,4301,4303,4306,4308,4311,4314,4317,4320,4322,4325,4327],{"class":950,"line":1610},[948,4302,3408],{"class":961},[948,4304,4305],{"class":1024}," filter",[948,4307,1028],{"class":961},[948,4309,4310],{"class":965}," { in: { field: ",[948,4312,4313],{"class":979},"'id'",[948,4315,4316],{"class":965}," }, values: ",[948,4318,4319],{"class":969},"ids",[948,4321,973],{"class":965},[948,4323,4324],{"class":961},"as",[948,4326,1021],{"class":961},[948,4328,983],{"class":965},[948,4330,4331,4333,4335,4337,4339,4342,4344,4346,4349,4352,4355,4357,4360,4362,4365,4368],{"class":950,"line":1625},[948,4332,3408],{"class":961},[948,4334,2024],{"class":1024},[948,4336,1028],{"class":961},[948,4338,1778],{"class":961},[948,4340,4341],{"class":1024}," failure",[948,4343,1176],{"class":965},[948,4345,3704],{"class":1024},[948,4347,4348],{"class":965},"({ op: ",[948,4350,4351],{"class":979},"'deleteMany'",[948,4353,4354],{"class":965},", collection: ",[948,4356,1790],{"class":979},[948,4358,4359],{"class":965},", select: [",[948,4361,4313],{"class":979},[948,4363,4364],{"class":965},"], ",[948,4366,4367],{"class":969},"filter",[948,4369,4370],{"class":965}," }));\n",[948,4372,4373,4376,4378,4380,4382,4385,4387,4390],{"class":950,"line":1630},[948,4374,4375],{"class":1024},"        expect",[948,4377,1176],{"class":965},[948,4379,1978],{"class":969},[948,4381,1187],{"class":965},[948,4383,4384],{"class":1024},"toBeInstanceOf",[948,4386,1176],{"class":965},[948,4388,4389],{"class":969},"QueryRejected",[948,4391,1237],{"class":965},[948,4393,4394,4396,4398,4400,4402,4405,4407,4410,4412,4415],{"class":950,"line":1636},[948,4395,4375],{"class":1024},[948,4397,1176],{"class":965},[948,4399,1978],{"class":969},[948,4401,1187],{"class":965},[948,4403,4404],{"class":1024},"toHaveProperty",[948,4406,1176],{"class":965},[948,4408,4409],{"class":979},"'message'",[948,4411,1181],{"class":965},[948,4413,4414],{"class":979},"'Deleted 3 of 6 customers before this error: Stripe refused'",[948,4416,1237],{"class":965},[948,4418,4419,4421,4423,4426,4428,4430,4432,4434,4436,4438,4440,4442,4444,4446,4449,4451,4454,4456,4458,4460,4462,4464,4466,4468,4471,4474,4477,4480,4482,4485,4487,4490,4492,4495],{"class":950,"line":1656},[948,4420,4375],{"class":1024},[948,4422,1176],{"class":965},[948,4424,4425],{"class":969},"sent",[948,4427,476],{"class":965},[948,4429,4367],{"class":1024},[948,4431,1975],{"class":965},[948,4433,3494],{"class":961},[948,4435,1220],{"class":965},[948,4437,1983],{"class":961},[948,4439,3411],{"class":969},[948,4441,476],{"class":965},[948,4443,3424],{"class":969},[948,4445,1159],{"class":961},[948,4447,4448],{"class":979}," 'DELETE'",[948,4450,1187],{"class":965},[948,4452,4453],{"class":1024},"map",[948,4455,1975],{"class":965},[948,4457,3494],{"class":961},[948,4459,1220],{"class":965},[948,4461,1983],{"class":961},[948,4463,3411],{"class":969},[948,4465,476],{"class":965},[948,4467,4116],{"class":969},[948,4469,4470],{"class":965},")).",[948,4472,4473],{"class":1024},"toEqual",[948,4475,4476],{"class":965},"([",[948,4478,4479],{"class":979},"'customers\u002Fcus_1'",[948,4481,1181],{"class":965},[948,4483,4484],{"class":979},"'customers\u002Fcus_2'",[948,4486,1181],{"class":965},[948,4488,4489],{"class":979},"'customers\u002Fcus_3'",[948,4491,1181],{"class":965},[948,4493,4494],{"class":979},"'customers\u002Fcus_4'",[948,4496,4497],{"class":965},"]);\n",[948,4499,4500],{"class":950,"line":1671},[948,4501,2032],{"class":965},[458,4503,4504],{},"Tie your declarations to your code with a test. This one plans every operator each collection declares, with one sample value each. It catches a declared operator the plan always refuses. It doesn't prove every value, combination or logical operator plans:",[938,4506,4508],{"className":3005,"code":4507,"filename":2944,"language":3007,"meta":944,"style":944},"\u002F\u002F Network-free tests for `planRequests`: which Stripe requests a filter becomes, and which filters\n\u002F\u002F are refused before any request.\nimport type { FilterOperator, FilterSerialized, Value } from '@monospace\u002Fextension-kit\u002Fdata-connector';\nimport type { ResourceSpec } from '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fresources';\nimport { QueryRejected } from '@monospace\u002Fextension-kit\u002Fdata-connector';\nimport { describe, expect, it } from 'vitest';\nimport { schemaDocument } from '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fcollections';\nimport { planRequests } from '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fplan';\nimport { findResource } from '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fresources';\n\n\u002F\u002F …\ndescribe('declarations and plan agree', () => {\n    \u002F\u002F Every operator a collection declares must plan for a sample value, so no declared operator is one the plan always refuses.\n    const samples: Record\u003Cstring, Value> = { id: 'x_1', email: 'a@x.test', productId: 'prod_1', active: true, type: 'recurring', created: '2026-01-01T00:00:00Z' };\n    const declared = (schemaDocument(true).collections ?? []).flatMap((collection) => Object.entries(collection.operations.readMany?.filter?.fields ?? {})\n        .flatMap(([field, { operators }]) => Object.keys(operators).map((operator) => [collection.name, field, operator] as const)));\n\n    const comparisons: readonly FilterOperator[] = ['equals', 'lessThan', 'lessThanOrEquals', 'greaterThan', 'greaterThanOrEquals'];\n\n    it.each(declared)('%s: `%s` with `%s` plans', (collection, field, operator) => {\n        const value = samples[field] ?? null;\n        const cmp = comparisons.find((candidate) => candidate === operator);\n        const filter: FilterSerialized = cmp === undefined ? { in: { field }, values: [value] } : { cmp, left: { field }, right: { value } };\n        expect(() => planRequests(resource(collection), filter)).not.toThrow();\n    });\n});\n",[506,4509,4510,4515,4520,4548,4568,4584,4608,4626,4644,4661,4665,4670,4686,4691,4750,4825,4897,4901,4948,4952,4991,5016,5051,5106,5143,5147],{"__ignoreMap":944},[948,4511,4512],{"class":950,"line":951},[948,4513,4514],{"class":954},"\u002F\u002F Network-free tests for `planRequests`: which Stripe requests a filter becomes, and which filters\n",[948,4516,4517],{"class":950,"line":958},[948,4518,4519],{"class":954},"\u002F\u002F are refused before any request.\n",[948,4521,4522,4524,4526,4528,4531,4533,4535,4537,4540,4542,4544,4546],{"class":950,"line":986},[948,4523,962],{"class":961},[948,4525,3031],{"class":961},[948,4527,966],{"class":965},[948,4529,4530],{"class":969},"FilterOperator",[948,4532,1181],{"class":965},[948,4534,3036],{"class":969},[948,4536,1181],{"class":965},[948,4538,4539],{"class":969},"Value",[948,4541,973],{"class":965},[948,4543,976],{"class":961},[948,4545,3053],{"class":979},[948,4547,983],{"class":965},[948,4549,4550,4552,4554,4556,4559,4561,4563,4566],{"class":950,"line":1002},[948,4551,962],{"class":961},[948,4553,3031],{"class":961},[948,4555,966],{"class":965},[948,4557,4558],{"class":969},"ResourceSpec",[948,4560,973],{"class":965},[948,4562,976],{"class":961},[948,4564,4565],{"class":979}," '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fresources'",[948,4567,983],{"class":965},[948,4569,4570,4572,4574,4576,4578,4580,4582],{"class":950,"line":1009},[948,4571,962],{"class":961},[948,4573,966],{"class":965},[948,4575,4389],{"class":969},[948,4577,973],{"class":965},[948,4579,976],{"class":961},[948,4581,3053],{"class":979},[948,4583,983],{"class":965},[948,4585,4586,4588,4590,4592,4594,4596,4598,4600,4602,4604,4606],{"class":950,"line":1015},[948,4587,962],{"class":961},[948,4589,966],{"class":965},[948,4591,3128],{"class":969},[948,4593,1181],{"class":965},[948,4595,3133],{"class":969},[948,4597,1181],{"class":965},[948,4599,3138],{"class":969},[948,4601,973],{"class":965},[948,4603,976],{"class":961},[948,4605,3150],{"class":979},[948,4607,983],{"class":965},[948,4609,4610,4612,4614,4617,4619,4621,4624],{"class":950,"line":1036},[948,4611,962],{"class":961},[948,4613,966],{"class":965},[948,4615,4616],{"class":969},"schemaDocument",[948,4618,973],{"class":965},[948,4620,976],{"class":961},[948,4622,4623],{"class":979}," '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fcollections'",[948,4625,983],{"class":965},[948,4627,4628,4630,4632,4635,4637,4639,4642],{"class":950,"line":1053},[948,4629,962],{"class":961},[948,4631,966],{"class":965},[948,4633,4634],{"class":969},"planRequests",[948,4636,973],{"class":965},[948,4638,976],{"class":961},[948,4640,4641],{"class":979}," '..\u002Fsrc\u002Fdata-connectors\u002Fstripe\u002Fplan'",[948,4643,983],{"class":965},[948,4645,4646,4648,4650,4653,4655,4657,4659],{"class":950,"line":1070},[948,4647,962],{"class":961},[948,4649,966],{"class":965},[948,4651,4652],{"class":969},"findResource",[948,4654,973],{"class":965},[948,4656,976],{"class":961},[948,4658,4565],{"class":979},[948,4660,983],{"class":965},[948,4662,4663],{"class":950,"line":1075},[948,4664,1006],{"emptyLinePlaceholder":1005},[948,4666,4667],{"class":950,"line":1081},[948,4668,4669],{"class":954},"\u002F\u002F …\n",[948,4671,4672,4674,4676,4679,4682,4684],{"class":950,"line":1095},[948,4673,3128],{"class":1024},[948,4675,1176],{"class":965},[948,4677,4678],{"class":979},"'declarations and plan agree'",[948,4680,4681],{"class":965},", () ",[948,4683,1983],{"class":961},[948,4685,1986],{"class":965},[948,4687,4688],{"class":950,"line":1120},[948,4689,4690],{"class":954},"    \u002F\u002F Every operator a collection declares must plan for a sample value, so no declared operator is one the plan always refuses.\n",[948,4692,4693,4695,4698,4700,4702,4704,4706,4708,4710,4712,4714,4717,4720,4722,4725,4728,4731,4734,4736,4739,4742,4745,4748],{"class":950,"line":1195},[948,4694,1098],{"class":961},[948,4696,4697],{"class":1024}," samples",[948,4699,2322],{"class":961},[948,4701,3582],{"class":1024},[948,4703,3287],{"class":965},[948,4705,3587],{"class":2876},[948,4707,1181],{"class":965},[948,4709,4539],{"class":1024},[948,4711,3595],{"class":965},[948,4713,1775],{"class":961},[948,4715,4716],{"class":965}," { id: ",[948,4718,4719],{"class":979},"'x_1'",[948,4721,3786],{"class":965},[948,4723,4724],{"class":979},"'a@x.test'",[948,4726,4727],{"class":965},", productId: ",[948,4729,4730],{"class":979},"'prod_1'",[948,4732,4733],{"class":965},", active: ",[948,4735,2429],{"class":1024},[948,4737,4738],{"class":965},", type: ",[948,4740,4741],{"class":979},"'recurring'",[948,4743,4744],{"class":965},", created: ",[948,4746,4747],{"class":979},"'2026-01-01T00:00:00Z'",[948,4749,3816],{"class":965},[948,4751,4752,4754,4757,4759,4761,4763,4765,4767,4769,4772,4775,4778,4781,4783,4786,4788,4790,4793,4795,4798,4800,4802,4804,4807,4809,4812,4814,4816,4818,4820,4822],{"class":950,"line":1240},[948,4753,1098],{"class":961},[948,4755,4756],{"class":1024}," declared",[948,4758,1028],{"class":961},[948,4760,1153],{"class":965},[948,4762,4616],{"class":1024},[948,4764,1176],{"class":965},[948,4766,2429],{"class":1024},[948,4768,1187],{"class":965},[948,4770,4771],{"class":969},"collections",[948,4773,4774],{"class":961}," ??",[948,4776,4777],{"class":965}," []).",[948,4779,4780],{"class":1024},"flatMap",[948,4782,1975],{"class":965},[948,4784,4785],{"class":961},"collection",[948,4787,1220],{"class":965},[948,4789,1983],{"class":961},[948,4791,4792],{"class":969}," Object",[948,4794,476],{"class":965},[948,4796,4797],{"class":1024},"entries",[948,4799,1176],{"class":965},[948,4801,4785],{"class":969},[948,4803,476],{"class":965},[948,4805,4806],{"class":969},"operations",[948,4808,476],{"class":965},[948,4810,4811],{"class":969},"readMany",[948,4813,1141],{"class":965},[948,4815,4367],{"class":969},[948,4817,1141],{"class":965},[948,4819,1723],{"class":969},[948,4821,4774],{"class":961},[948,4823,4824],{"class":965}," {})\n",[948,4826,4827,4830,4832,4835,4838,4840,4843,4846,4848,4850,4852,4855,4857,4859,4861,4863,4865,4868,4870,4872,4874,4876,4878,4881,4883,4885,4887,4889,4891,4893,4895],{"class":950,"line":1246},[948,4828,4829],{"class":965},"        .",[948,4831,4780],{"class":1024},[948,4833,4834],{"class":965},"(([",[948,4836,4837],{"class":961},"field",[948,4839,1793],{"class":965},[948,4841,4842],{"class":961},"operators",[948,4844,4845],{"class":965}," }]) ",[948,4847,1983],{"class":961},[948,4849,4792],{"class":969},[948,4851,476],{"class":965},[948,4853,4854],{"class":1024},"keys",[948,4856,1176],{"class":965},[948,4858,4842],{"class":969},[948,4860,1187],{"class":965},[948,4862,4453],{"class":1024},[948,4864,1975],{"class":965},[948,4866,4867],{"class":961},"operator",[948,4869,1220],{"class":965},[948,4871,1983],{"class":961},[948,4873,1766],{"class":965},[948,4875,4785],{"class":969},[948,4877,476],{"class":965},[948,4879,4880],{"class":969},"name",[948,4882,1181],{"class":965},[948,4884,4837],{"class":969},[948,4886,1181],{"class":965},[948,4888,4867],{"class":969},[948,4890,1772],{"class":965},[948,4892,4324],{"class":961},[948,4894,1021],{"class":961},[948,4896,4182],{"class":965},[948,4898,4899],{"class":950,"line":1298},[948,4900,1006],{"emptyLinePlaceholder":1005},[948,4902,4903,4905,4908,4910,4913,4916,4919,4921,4923,4926,4928,4931,4933,4936,4938,4941,4943,4946],{"class":950,"line":1308},[948,4904,1098],{"class":961},[948,4906,4907],{"class":1024}," comparisons",[948,4909,2322],{"class":961},[948,4911,4912],{"class":961}," readonly",[948,4914,4915],{"class":1024}," FilterOperator",[948,4917,4918],{"class":965},"[] ",[948,4920,1775],{"class":961},[948,4922,1766],{"class":965},[948,4924,4925],{"class":979},"'equals'",[948,4927,1181],{"class":965},[948,4929,4930],{"class":979},"'lessThan'",[948,4932,1181],{"class":965},[948,4934,4935],{"class":979},"'lessThanOrEquals'",[948,4937,1181],{"class":965},[948,4939,4940],{"class":979},"'greaterThan'",[948,4942,1181],{"class":965},[948,4944,4945],{"class":979},"'greaterThanOrEquals'",[948,4947,1506],{"class":965},[948,4949,4950],{"class":950,"line":1591},[948,4951,1006],{"emptyLinePlaceholder":1005},[948,4953,4954,4956,4958,4961,4963,4966,4969,4972,4975,4977,4979,4981,4983,4985,4987,4989],{"class":950,"line":1610},[948,4955,3916],{"class":969},[948,4957,476],{"class":965},[948,4959,4960],{"class":1024},"each",[948,4962,1176],{"class":965},[948,4964,4965],{"class":969},"declared",[948,4967,4968],{"class":965},")(",[948,4970,4971],{"class":979},"'%s: `%s` with `%s` plans'",[948,4973,4974],{"class":965},", (",[948,4976,4785],{"class":961},[948,4978,1181],{"class":965},[948,4980,4837],{"class":961},[948,4982,1181],{"class":965},[948,4984,4867],{"class":961},[948,4986,1220],{"class":965},[948,4988,1983],{"class":961},[948,4990,1986],{"class":965},[948,4992,4993,4995,4998,5000,5002,5005,5007,5009,5011,5014],{"class":950,"line":1625},[948,4994,3408],{"class":961},[948,4996,4997],{"class":1024}," value",[948,4999,1028],{"class":961},[948,5001,4697],{"class":969},[948,5003,5004],{"class":965},"[",[948,5006,4837],{"class":969},[948,5008,1772],{"class":965},[948,5010,1150],{"class":961},[948,5012,5013],{"class":1024}," null",[948,5015,983],{"class":965},[948,5017,5018,5020,5023,5025,5027,5029,5032,5034,5037,5039,5041,5044,5046,5049],{"class":950,"line":1630},[948,5019,3408],{"class":961},[948,5021,5022],{"class":1024}," cmp",[948,5024,1028],{"class":961},[948,5026,4907],{"class":969},[948,5028,476],{"class":965},[948,5030,5031],{"class":1024},"find",[948,5033,1975],{"class":965},[948,5035,5036],{"class":961},"candidate",[948,5038,1220],{"class":965},[948,5040,1983],{"class":961},[948,5042,5043],{"class":969}," candidate",[948,5045,1159],{"class":961},[948,5047,5048],{"class":969}," operator",[948,5050,1237],{"class":965},[948,5052,5053,5055,5057,5059,5062,5064,5066,5068,5070,5072,5075,5077,5080,5083,5086,5088,5090,5093,5096,5098,5101,5103],{"class":950,"line":1636},[948,5054,3408],{"class":961},[948,5056,4305],{"class":1024},[948,5058,2322],{"class":961},[948,5060,5061],{"class":1024}," FilterSerialized",[948,5063,1028],{"class":961},[948,5065,5022],{"class":969},[948,5067,1159],{"class":961},[948,5069,1162],{"class":1024},[948,5071,1165],{"class":961},[948,5073,5074],{"class":965}," { in: { ",[948,5076,4837],{"class":969},[948,5078,5079],{"class":965}," }, values: [",[948,5081,5082],{"class":969},"value",[948,5084,5085],{"class":965},"] } ",[948,5087,2322],{"class":961},[948,5089,966],{"class":965},[948,5091,5092],{"class":969},"cmp",[948,5094,5095],{"class":965},", left: { ",[948,5097,4837],{"class":969},[948,5099,5100],{"class":965}," }, right: { ",[948,5102,5082],{"class":969},[948,5104,5105],{"class":965}," } };\n",[948,5107,5108,5110,5112,5114,5117,5119,5122,5124,5126,5129,5131,5133,5136,5138,5141],{"class":950,"line":1656},[948,5109,4375],{"class":1024},[948,5111,3304],{"class":965},[948,5113,1983],{"class":961},[948,5115,5116],{"class":1024}," planRequests",[948,5118,1176],{"class":965},[948,5120,5121],{"class":1024},"resource",[948,5123,1176],{"class":965},[948,5125,4785],{"class":969},[948,5127,5128],{"class":965},"), ",[948,5130,4367],{"class":969},[948,5132,4470],{"class":965},[948,5134,5135],{"class":969},"not",[948,5137,476],{"class":965},[948,5139,5140],{"class":1024},"toThrow",[948,5142,3538],{"class":965},[948,5144,5145],{"class":950,"line":1671},[948,5146,2032],{"class":965},[948,5148,5149],{"class":950,"line":1676},[948,5150,3543],{"class":965},[458,5152,5153,5154,5157],{},"Unit tests share your copy of the kit, so ",[506,5155,5156],{},"instanceof"," works in them.",[458,5159,5160,5161,5164,5165,5167,5168,5171],{},"When it works, the linter and ",[506,5162,5163],{},"tsc"," exit ",[506,5166,3803],{},", and Vitest reports every test passed (",[506,5169,5170],{},"Tests  110 passed (110)"," for the Stripe connector).",[454,5173,558],{"id":5174},"_2-build",[458,5176,5177,5178,1181,5180,5183,5184,5187,5188,5191,5192,5195,5196,476],{},"Build the artifact with the CLI. The build parses the bundled output and checks that it has a default export and no ",[506,5179,962],{},[506,5181,5182],{},"export … from",", dynamic ",[506,5185,5186],{},"import()"," or direct ",[506,5189,5190],{},"require(...)"," left. It doesn't run your code, and a ",[506,5193,5194],{},"require"," it can't see (e.g., one called with spread arguments) passes. See ",[467,5197,5199],{"href":5198},"\u002Freference\u002Fextensions\u002Fcli#data-connector-bundles","Data Connector Bundles",[2496,5201,5202,5216,5227,5238],{"sync":2498},[938,5203,5205],{"className":2501,"code":5204,"filename":2503,"language":2504,"meta":944,"style":944},"npm run build\n",[506,5206,5207],{"__ignoreMap":944},[948,5208,5209,5211,5213],{"class":950,"line":951},[948,5210,2503],{"class":1024},[948,5212,2513],{"class":979},[948,5214,5215],{"class":979}," build\n",[938,5217,5219],{"className":2501,"code":5218,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm build\n",[506,5220,5221],{"__ignoreMap":944},[948,5222,5223,5225],{"class":950,"line":951},[948,5224,2520],{"class":1024},[948,5226,5215],{"class":979},[938,5228,5230],{"className":2501,"code":5229,"filename":2532,"language":2504,"meta":944,"style":944},"yarn build\n",[506,5231,5232],{"__ignoreMap":944},[948,5233,5234,5236],{"class":950,"line":951},[948,5235,2532],{"class":1024},[948,5237,5215],{"class":979},[938,5239,5241],{"className":2501,"code":5240,"filename":2544,"language":2504,"meta":944,"style":944},"bun run build\n",[506,5242,5243],{"__ignoreMap":944},[948,5244,5245,5247,5249],{"class":950,"line":951},[948,5246,2544],{"class":1024},[948,5248,2513],{"class":979},[948,5250,5215],{"class":979},[938,5252,5258],{"className":5253,"code":5255,"filename":5256,"language":5257,"meta":944},[5254],"language-text","example\u002Fstripe-connector  example\u002Fstripe-connector.js\nArtifact                  \u002Fhome\u002Fyou\u002Fstripe\u002Fdist\n","output","text",[506,5259,5255],{"__ignoreMap":944},[458,5261,5262,5263,5266,5267,5270,5271,5274,5275,5279],{},"With ",[506,5264,5265],{},"--json",", check that ",[506,5268,5269],{},"outcome"," is ",[506,5272,5273],{},"ok",". See ",[467,5276,5278],{"href":5277},"\u002Freference\u002Fextensions\u002Fcli#build-an-extension","Build an Extension"," for the build steps and errors.",[458,5281,5282,5283,5286,5287,5290,5291,1187],{},"When it works, ",[506,5284,5285],{},"dist\u002F"," holds ",[506,5288,5289],{},"manifest.json"," and one JavaScript file per entry (e.g., ",[506,5292,5293],{},"dist\u002Fexample\u002Fstripe-connector.js",[454,5295,5297],{"id":5296},"_3-run-an-artifact-smoke","3. Run an Artifact Smoke",[458,5299,5300,5301,5303],{},"Import the built file in Node and call ",[506,5302,3613],{}," and the handlers directly. No Engine, no harness: the default export is your connector definition.",[2496,5305,5306,5320,5331,5342],{"sync":2498},[938,5307,5309],{"className":2501,"code":5308,"filename":2503,"language":2504,"meta":944,"style":944},"npm run seed\n",[506,5310,5311],{"__ignoreMap":944},[948,5312,5313,5315,5317],{"class":950,"line":951},[948,5314,2503],{"class":1024},[948,5316,2513],{"class":979},[948,5318,5319],{"class":979}," seed\n",[938,5321,5323],{"className":2501,"code":5322,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm seed\n",[506,5324,5325],{"__ignoreMap":944},[948,5326,5327,5329],{"class":950,"line":951},[948,5328,2520],{"class":1024},[948,5330,5319],{"class":979},[938,5332,5334],{"className":2501,"code":5333,"filename":2532,"language":2504,"meta":944,"style":944},"yarn seed\n",[506,5335,5336],{"__ignoreMap":944},[948,5337,5338,5340],{"class":950,"line":951},[948,5339,2532],{"class":1024},[948,5341,5319],{"class":979},[938,5343,5345],{"className":2501,"code":5344,"filename":2544,"language":2504,"meta":944,"style":944},"bun run seed\n",[506,5346,5347],{"__ignoreMap":944},[948,5348,5349,5351,5353],{"class":950,"line":951},[948,5350,2544],{"class":1024},[948,5352,2513],{"class":979},[948,5354,5319],{"class":979},[2496,5356,5357,5371,5382,5393],{"sync":2498},[938,5358,5360],{"className":2501,"code":5359,"filename":2503,"language":2504,"meta":944,"style":944},"npm run smoke\n",[506,5361,5362],{"__ignoreMap":944},[948,5363,5364,5366,5368],{"class":950,"line":951},[948,5365,2503],{"class":1024},[948,5367,2513],{"class":979},[948,5369,5370],{"class":979}," smoke\n",[938,5372,5374],{"className":2501,"code":5373,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm smoke\n",[506,5375,5376],{"__ignoreMap":944},[948,5377,5378,5380],{"class":950,"line":951},[948,5379,2520],{"class":1024},[948,5381,5370],{"class":979},[938,5383,5385],{"className":2501,"code":5384,"filename":2532,"language":2504,"meta":944,"style":944},"yarn smoke\n",[506,5386,5387],{"__ignoreMap":944},[948,5388,5389,5391],{"class":950,"line":951},[948,5390,2532],{"class":1024},[948,5392,5370],{"class":979},[938,5394,5396],{"className":2501,"code":5395,"filename":2544,"language":2504,"meta":944,"style":944},"bun run smoke\n",[506,5397,5398],{"__ignoreMap":944},[948,5399,5400,5402,5404],{"class":950,"line":951},[948,5401,2544],{"class":1024},[948,5403,2513],{"class":979},[948,5405,5370],{"class":979},[458,5407,5408,5409,5411],{},"The Stripe smoke wraps ",[506,5410,865],{}," first, so every check can see the requests to the external data source, and can change Stripe between two of them. Then it imports the artifact:",[1333,5413,5414],{},[1336,5415,5417],{"label":5416},"scripts\u002Fsmoke.mjs (setup)",[938,5418,5420],{"className":940,"code":5419,"filename":761,"language":943,"meta":944,"style":944},"\u002F\u002F Artifact smoke: imports the built connector and drives it against a Stripe test account.\n\u002F\u002F Expectations come from Stripe's own answers or the namespaced fixture (`npm run seed`), never\n\u002F\u002F account totals. Run `npm run build` first.\nimport assert from 'node:assert\u002Fstrict';\nimport { readFile } from 'node:fs\u002Fpromises';\nimport { ErrorCode } from '@monospace\u002Fextension-kit\u002Fdata-connector';\nimport { FIXTURE_EMAIL_DOMAIN, loadApiKey, STRIPE_API_VERSION, stripeApi } from '.\u002Fstripe.mjs';\n\nconst apiKey = loadApiKey();\nconst stripe = stripeApi(apiKey);\nconst requests = [];\nconst realFetch = globalThis.fetch;\n\u002F** Runs once, after the next request's answer arrives: lets a check change Stripe between two requests. *\u002F\nlet afterNextRequest;\n\nglobalThis.fetch = async (url, init) => {\n    requests.push({ method: init.method, url: new URL(url), version: init.headers['Stripe-Version'], signal: init.signal });\n    const response = await realFetch(url, init);\n    const hook = afterNextRequest;\n    afterNextRequest = undefined;\n    await hook?.();\n    return response;\n};\n\nconst manifest = JSON.parse(await readFile(new URL('..\u002Fdist\u002Fmanifest.json', import.meta.url), 'utf8'));\nconst { default: descriptor } = await import(new URL('..\u002Fdist\u002Fexample\u002Fstripe-connector.js', import.meta.url).href);\nconst connector = await descriptor.setup({ config: { apiKey } });\nconst checks = [];\n\nasync function check(name, run) {\n    requests.length = 0;\n    await run();\n    checks.push(name);\n}\n\nasync function query(operation, target = connector) {\n    return await target.query({ query: operation });\n}\n\nasync function read(collection, options = {}) {\n    const result = await query({ op: 'readMany', collection, select: ['id'], ...options });\n    return result.records;\n}\n\nasync function ids(collection, options) {\n    const records = await read(collection, options);\n    return records.map((record) => record.id);\n}\n\nfunction compare(cmp, field, value) {\n    return { cmp, left: { field }, right: { value } };\n}\n",[506,5421,5422,5427,5432,5437,5451,5469,5486,5515,5519,5532,5549,5560,5578,5583,5592,5596,5623,5679,5702,5715,5726,5735,5743,5748,5752,5807,5860,5884,5895,5899,5919,5935,5943,5958,5962,5966,5989,6008,6012,6016,6038,6072,6085,6089,6093,6111,6134,6162,6166,6170,6191,6209],{"__ignoreMap":944},[948,5423,5424],{"class":950,"line":951},[948,5425,5426],{"class":954},"\u002F\u002F Artifact smoke: imports the built connector and drives it against a Stripe test account.\n",[948,5428,5429],{"class":950,"line":958},[948,5430,5431],{"class":954},"\u002F\u002F Expectations come from Stripe's own answers or the namespaced fixture (`npm run seed`), never\n",[948,5433,5434],{"class":950,"line":986},[948,5435,5436],{"class":954},"\u002F\u002F account totals. Run `npm run build` first.\n",[948,5438,5439,5441,5444,5446,5449],{"class":950,"line":1002},[948,5440,962],{"class":961},[948,5442,5443],{"class":969}," assert",[948,5445,994],{"class":961},[948,5447,5448],{"class":979}," 'node:assert\u002Fstrict'",[948,5450,983],{"class":965},[948,5452,5453,5455,5457,5460,5462,5464,5467],{"class":950,"line":1009},[948,5454,962],{"class":961},[948,5456,966],{"class":965},[948,5458,5459],{"class":969},"readFile",[948,5461,973],{"class":965},[948,5463,976],{"class":961},[948,5465,5466],{"class":979}," 'node:fs\u002Fpromises'",[948,5468,983],{"class":965},[948,5470,5471,5473,5475,5478,5480,5482,5484],{"class":950,"line":1015},[948,5472,962],{"class":961},[948,5474,966],{"class":965},[948,5476,5477],{"class":969},"ErrorCode",[948,5479,973],{"class":965},[948,5481,976],{"class":961},[948,5483,3053],{"class":979},[948,5485,983],{"class":965},[948,5487,5488,5490,5492,5494,5496,5498,5500,5503,5505,5507,5509,5511,5513],{"class":950,"line":1036},[948,5489,962],{"class":961},[948,5491,966],{"class":965},[948,5493,1366],{"class":969},[948,5495,1181],{"class":965},[948,5497,1376],{"class":969},[948,5499,1181],{"class":965},[948,5501,5502],{"class":969},"STRIPE_API_VERSION",[948,5504,1181],{"class":965},[948,5506,1381],{"class":969},[948,5508,973],{"class":965},[948,5510,976],{"class":961},[948,5512,1388],{"class":979},[948,5514,983],{"class":965},[948,5516,5517],{"class":950,"line":1053},[948,5518,1006],{"emptyLinePlaceholder":1005},[948,5520,5521,5523,5526,5528,5530],{"class":950,"line":1070},[948,5522,1399],{"class":961},[948,5524,5525],{"class":1024}," apiKey",[948,5527,1028],{"class":961},[948,5529,1089],{"class":1024},[948,5531,3538],{"class":965},[948,5533,5534,5536,5538,5540,5542,5544,5547],{"class":950,"line":1075},[948,5535,1399],{"class":961},[948,5537,1402],{"class":1024},[948,5539,1028],{"class":961},[948,5541,1407],{"class":1024},[948,5543,1176],{"class":965},[948,5545,5546],{"class":969},"apiKey",[948,5548,1237],{"class":965},[948,5550,5551,5553,5556,5558],{"class":950,"line":1081},[948,5552,1399],{"class":961},[948,5554,5555],{"class":1024}," requests",[948,5557,1028],{"class":961},[948,5559,1696],{"class":965},[948,5561,5562,5564,5567,5569,5572,5574,5576],{"class":950,"line":1095},[948,5563,1399],{"class":961},[948,5565,5566],{"class":1024}," realFetch",[948,5568,1028],{"class":961},[948,5570,5571],{"class":969}," globalThis",[948,5573,476],{"class":965},[948,5575,865],{"class":969},[948,5577,983],{"class":965},[948,5579,5580],{"class":950,"line":1120},[948,5581,5582],{"class":954},"\u002F** Runs once, after the next request's answer arrives: lets a check change Stripe between two requests. *\u002F\n",[948,5584,5585,5587,5590],{"class":950,"line":1195},[948,5586,3245],{"class":961},[948,5588,5589],{"class":969}," afterNextRequest",[948,5591,983],{"class":965},[948,5593,5594],{"class":950,"line":1240},[948,5595,1006],{"emptyLinePlaceholder":1005},[948,5597,5598,5601,5603,5605,5607,5609,5611,5613,5615,5617,5619,5621],{"class":950,"line":1246},[948,5599,5600],{"class":969},"globalThis",[948,5602,476],{"class":965},[948,5604,865],{"class":1024},[948,5606,1028],{"class":961},[948,5608,4059],{"class":961},[948,5610,1153],{"class":965},[948,5612,3430],{"class":961},[948,5614,1181],{"class":965},[948,5616,3419],{"class":961},[948,5618,1220],{"class":965},[948,5620,1983],{"class":961},[948,5622,1986],{"class":965},[948,5624,5625,5628,5630,5632,5635,5637,5639,5641,5644,5646,5648,5650,5652,5655,5657,5659,5662,5664,5667,5670,5672,5674,5677],{"class":950,"line":1298},[948,5626,5627],{"class":969},"    requests",[948,5629,476],{"class":965},[948,5631,1890],{"class":1024},[948,5633,5634],{"class":965},"({ method: ",[948,5636,3419],{"class":969},[948,5638,476],{"class":965},[948,5640,3424],{"class":969},[948,5642,5643],{"class":965},", url: ",[948,5645,3466],{"class":961},[948,5647,3376],{"class":1024},[948,5649,1176],{"class":965},[948,5651,3430],{"class":969},[948,5653,5654],{"class":965},"), version: ",[948,5656,3419],{"class":969},[948,5658,476],{"class":965},[948,5660,5661],{"class":969},"headers",[948,5663,5004],{"class":965},[948,5665,5666],{"class":979},"'Stripe-Version'",[948,5668,5669],{"class":965},"], signal: ",[948,5671,3419],{"class":969},[948,5673,476],{"class":965},[948,5675,5676],{"class":969},"signal",[948,5678,1805],{"class":965},[948,5680,5681,5683,5686,5688,5690,5692,5694,5696,5698,5700],{"class":950,"line":1308},[948,5682,1098],{"class":961},[948,5684,5685],{"class":1024}," response",[948,5687,1028],{"class":961},[948,5689,1778],{"class":961},[948,5691,5566],{"class":1024},[948,5693,1176],{"class":965},[948,5695,3430],{"class":969},[948,5697,1181],{"class":965},[948,5699,3419],{"class":969},[948,5701,1237],{"class":965},[948,5703,5704,5706,5709,5711,5713],{"class":950,"line":1591},[948,5705,1098],{"class":961},[948,5707,5708],{"class":1024}," hook",[948,5710,1028],{"class":961},[948,5712,5589],{"class":969},[948,5714,983],{"class":965},[948,5716,5717,5720,5722,5724],{"class":950,"line":1610},[948,5718,5719],{"class":969},"    afterNextRequest",[948,5721,1028],{"class":961},[948,5723,1162],{"class":1024},[948,5725,983],{"class":965},[948,5727,5728,5730,5732],{"class":950,"line":1625},[948,5729,1837],{"class":961},[948,5731,5708],{"class":1024},[948,5733,5734],{"class":965},"?.();\n",[948,5736,5737,5739,5741],{"class":950,"line":1630},[948,5738,1301],{"class":961},[948,5740,5685],{"class":969},[948,5742,983],{"class":965},[948,5744,5745],{"class":950,"line":1636},[948,5746,5747],{"class":965},"};\n",[948,5749,5750],{"class":950,"line":1656},[948,5751,1006],{"emptyLinePlaceholder":1005},[948,5753,5754,5756,5759,5761,5764,5766,5769,5771,5774,5777,5779,5781,5783,5785,5788,5790,5792,5794,5796,5798,5800,5802,5804],{"class":950,"line":1671},[948,5755,1399],{"class":961},[948,5757,5758],{"class":1024}," manifest",[948,5760,1028],{"class":961},[948,5762,5763],{"class":1024}," JSON",[948,5765,476],{"class":965},[948,5767,5768],{"class":1024},"parse",[948,5770,1176],{"class":965},[948,5772,5773],{"class":961},"await",[948,5775,5776],{"class":1024}," readFile",[948,5778,1176],{"class":965},[948,5780,3466],{"class":961},[948,5782,3376],{"class":1024},[948,5784,1176],{"class":965},[948,5786,5787],{"class":979},"'..\u002Fdist\u002Fmanifest.json'",[948,5789,1181],{"class":965},[948,5791,962],{"class":961},[948,5793,476],{"class":965},[948,5795,2877],{"class":2876},[948,5797,476],{"class":965},[948,5799,3430],{"class":969},[948,5801,5128],{"class":965},[948,5803,1184],{"class":979},[948,5805,5806],{"class":965},"));\n",[948,5808,5809,5811,5813,5816,5818,5821,5823,5825,5827,5830,5832,5834,5836,5838,5841,5843,5845,5847,5849,5851,5853,5855,5858],{"class":950,"line":1676},[948,5810,1399],{"class":961},[948,5812,966],{"class":965},[948,5814,5815],{"class":961},"default",[948,5817,1866],{"class":965},[948,5819,5820],{"class":1024},"descriptor",[948,5822,973],{"class":965},[948,5824,1775],{"class":961},[948,5826,1778],{"class":961},[948,5828,5829],{"class":1024}," import",[948,5831,1176],{"class":965},[948,5833,3466],{"class":961},[948,5835,3376],{"class":1024},[948,5837,1176],{"class":965},[948,5839,5840],{"class":979},"'..\u002Fdist\u002Fexample\u002Fstripe-connector.js'",[948,5842,1181],{"class":965},[948,5844,962],{"class":961},[948,5846,476],{"class":965},[948,5848,2877],{"class":2876},[948,5850,476],{"class":965},[948,5852,3430],{"class":969},[948,5854,1187],{"class":965},[948,5856,5857],{"class":969},"href",[948,5859,1237],{"class":965},[948,5861,5862,5864,5866,5868,5870,5873,5875,5877,5880,5882],{"class":950,"line":1681},[948,5863,1399],{"class":961},[948,5865,3159],{"class":1024},[948,5867,1028],{"class":961},[948,5869,1778],{"class":961},[948,5871,5872],{"class":969}," descriptor",[948,5874,476],{"class":965},[948,5876,3613],{"class":1024},[948,5878,5879],{"class":965},"({ config: { ",[948,5881,5546],{"class":969},[948,5883,3629],{"class":965},[948,5885,5886,5888,5891,5893],{"class":950,"line":1686},[948,5887,1399],{"class":961},[948,5889,5890],{"class":1024}," checks",[948,5892,1028],{"class":961},[948,5894,1696],{"class":965},[948,5896,5897],{"class":950,"line":1699},[948,5898,1006],{"emptyLinePlaceholder":1005},[948,5900,5901,5903,5905,5908,5910,5912,5914,5917],{"class":950,"line":1704},[948,5902,3368],{"class":961},[948,5904,1086],{"class":961},[948,5906,5907],{"class":1024}," check",[948,5909,1176],{"class":965},[948,5911,4880],{"class":961},[948,5913,1181],{"class":965},[948,5915,5916],{"class":961},"run",[948,5918,1733],{"class":965},[948,5920,5921,5923,5925,5928,5930,5933],{"class":950,"line":1736},[948,5922,5627],{"class":969},[948,5924,476],{"class":965},[948,5926,5927],{"class":2876},"length",[948,5929,1028],{"class":961},[948,5931,5932],{"class":1024}," 0",[948,5934,983],{"class":965},[948,5936,5937,5939,5941],{"class":950,"line":1761},[948,5938,1837],{"class":961},[948,5940,2513],{"class":1024},[948,5942,3538],{"class":965},[948,5944,5945,5948,5950,5952,5954,5956],{"class":950,"line":1808},[948,5946,5947],{"class":969},"    checks",[948,5949,476],{"class":965},[948,5951,1890],{"class":1024},[948,5953,1176],{"class":965},[948,5955,4880],{"class":969},[948,5957,1237],{"class":965},[948,5959,5960],{"class":950,"line":1829},[948,5961,1311],{"class":965},[948,5963,5964],{"class":950,"line":1834},[948,5965,1006],{"emptyLinePlaceholder":1005},[948,5967,5968,5970,5972,5974,5976,5978,5980,5983,5985,5987],{"class":950,"line":1882},[948,5969,3368],{"class":961},[948,5971,1086],{"class":961},[948,5973,3646],{"class":1024},[948,5975,1176],{"class":965},[948,5977,3710],{"class":961},[948,5979,1181],{"class":965},[948,5981,5982],{"class":961},"target",[948,5984,1028],{"class":961},[948,5986,3159],{"class":969},[948,5988,1733],{"class":965},[948,5990,5991,5993,5995,5998,6000,6002,6004,6006],{"class":950,"line":1899},[948,5992,1301],{"class":961},[948,5994,1778],{"class":961},[948,5996,5997],{"class":969}," target",[948,5999,476],{"class":965},[948,6001,3704],{"class":1024},[948,6003,3707],{"class":965},[948,6005,3710],{"class":969},[948,6007,1805],{"class":965},[948,6009,6010],{"class":950,"line":1904},[948,6011,1311],{"class":965},[948,6013,6014],{"class":950,"line":1909},[948,6015,1006],{"emptyLinePlaceholder":1005},[948,6017,6018,6020,6022,6025,6027,6029,6031,6034,6036],{"class":950,"line":1942},[948,6019,3368],{"class":961},[948,6021,1086],{"class":961},[948,6023,6024],{"class":1024}," read",[948,6026,1176],{"class":965},[948,6028,4785],{"class":961},[948,6030,1181],{"class":965},[948,6032,6033],{"class":961},"options",[948,6035,1028],{"class":961},[948,6037,3600],{"class":965},[948,6039,6040,6042,6045,6047,6049,6051,6053,6056,6058,6060,6062,6064,6066,6068,6070],{"class":950,"line":1989},[948,6041,1098],{"class":961},[948,6043,6044],{"class":1024}," result",[948,6046,1028],{"class":961},[948,6048,1778],{"class":961},[948,6050,3646],{"class":1024},[948,6052,4348],{"class":965},[948,6054,6055],{"class":979},"'readMany'",[948,6057,1181],{"class":965},[948,6059,4785],{"class":969},[948,6061,4359],{"class":965},[948,6063,4313],{"class":979},[948,6065,4364],{"class":965},[948,6067,1720],{"class":961},[948,6069,6033],{"class":969},[948,6071,1805],{"class":965},[948,6073,6074,6076,6078,6080,6083],{"class":950,"line":2018},[948,6075,1301],{"class":961},[948,6077,6044],{"class":969},[948,6079,476],{"class":965},[948,6081,6082],{"class":969},"records",[948,6084,983],{"class":965},[948,6086,6087],{"class":950,"line":2029},[948,6088,1311],{"class":965},[948,6090,6091],{"class":950,"line":2035},[948,6092,1006],{"emptyLinePlaceholder":1005},[948,6094,6095,6097,6099,6101,6103,6105,6107,6109],{"class":950,"line":2040},[948,6096,3368],{"class":961},[948,6098,1086],{"class":961},[948,6100,3938],{"class":1024},[948,6102,1176],{"class":965},[948,6104,4785],{"class":961},[948,6106,1181],{"class":965},[948,6108,6033],{"class":961},[948,6110,1733],{"class":965},[948,6112,6113,6115,6118,6120,6122,6124,6126,6128,6130,6132],{"class":950,"line":2055},[948,6114,1098],{"class":961},[948,6116,6117],{"class":1024}," records",[948,6119,1028],{"class":961},[948,6121,1778],{"class":961},[948,6123,6024],{"class":1024},[948,6125,1176],{"class":965},[948,6127,4785],{"class":969},[948,6129,1181],{"class":965},[948,6131,6033],{"class":969},[948,6133,1237],{"class":965},[948,6135,6136,6138,6140,6142,6144,6146,6149,6151,6153,6156,6158,6160],{"class":950,"line":2100},[948,6137,1301],{"class":961},[948,6139,6117],{"class":969},[948,6141,476],{"class":965},[948,6143,4453],{"class":1024},[948,6145,1975],{"class":965},[948,6147,6148],{"class":961},"record",[948,6150,1220],{"class":965},[948,6152,1983],{"class":961},[948,6154,6155],{"class":969}," record",[948,6157,476],{"class":965},[948,6159,1920],{"class":969},[948,6161,1237],{"class":965},[948,6163,6164],{"class":950,"line":2116},[948,6165,1311],{"class":965},[948,6167,6168],{"class":950,"line":2122},[948,6169,1006],{"emptyLinePlaceholder":1005},[948,6171,6172,6174,6177,6179,6181,6183,6185,6187,6189],{"class":950,"line":2127},[948,6173,3725],{"class":961},[948,6175,6176],{"class":1024}," compare",[948,6178,1176],{"class":965},[948,6180,5092],{"class":961},[948,6182,1181],{"class":965},[948,6184,4837],{"class":961},[948,6186,1181],{"class":965},[948,6188,5082],{"class":961},[948,6190,1733],{"class":965},[948,6192,6193,6195,6197,6199,6201,6203,6205,6207],{"class":950,"line":2161},[948,6194,1301],{"class":961},[948,6196,966],{"class":965},[948,6198,5092],{"class":969},[948,6200,5095],{"class":965},[948,6202,4837],{"class":969},[948,6204,5100],{"class":965},[948,6206,5082],{"class":969},[948,6208,5105],{"class":965},[948,6210,6211],{"class":950,"line":2166},[948,6212,1311],{"class":965},[884,6214,6215,6221,6231],{},[887,6216,6217,6220],{},[462,6218,6219],{},"Test the built file, not your sources."," The artifact is what Engine loads.",[887,6222,6223,6226,6227,6230],{},[462,6224,6225],{},"Name every check."," The script ends by printing ",[506,6228,6229],{},"{ ok, checks }",", so a run shows what it covered.",[887,6232,6233,6236,6237,6239,6240,5274,6243,476],{},[462,6234,6235],{},"Compare error codes, not classes."," The artifact bundles its own copy of the kit, so ",[506,6238,5156],{}," against your import fails. Read ",[506,6241,6242],{},"error.monospace.code",[467,6244,6246],{"href":6245},"\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Ferrors#_7-check-what-callers-see","Check What Callers See",[458,6248,6249,6251],{},[506,6250,942],{}," is a small Stripe client of its own. It doesn't import the connector, so comparing the two proves something.",[2450,6253,6255],{"id":6254},"cover-these-in-the-artifact-smoke","Cover These in the Artifact Smoke",[515,6257,6258,6268],{},[518,6259,6260],{},[521,6261,6262,6265],{},[524,6263,6264],{},"Area",[524,6266,6267],{},"Check",[534,6269,6270,6295,6314,6336,6351,6359,6367,6378,6389,6409,6428,6436,6444],{},[521,6271,6272,6275],{},[539,6273,6274],{},"Network permissions",[539,6276,6277,6280,6281,6284,6285,6288,6289,5270,6292],{},[506,6278,6279],{},"dist\u002Fmanifest.json"," declares exactly your fixed hosts. With a ",[506,6282,6283],{},"preflight",", it returns ",[506,6286,6287],{},"{ permissions }"," with exactly the hosts your configuration adds; the manifest hosts and those make up the required set. Without one, ",[506,6290,6291],{},"descriptor.preflight",[506,6293,6294],{},"undefined",[521,6296,6297,6299],{},[539,6298,266],{},[539,6300,6301,6303,6304,1153,6307,1181,6310,6313],{},[506,6302,3613],{}," throws ",[506,6305,6306],{},"InvalidConfiguration",[506,6308,6309],{},"ErrorCode.InvalidConfiguration",[506,6311,6312],{},"2001",") for each invalid configuration, and makes no request",[521,6315,6316,6319],{},[539,6317,6318],{},"Credentials",[539,6320,6321,6322,6325,6326,1153,6329,1181,6332,6335],{},"If your external data source takes a credential and you implement the optional ",[506,6323,6324],{},"testConnection",", a wrong credential makes it throw ",[506,6327,6328],{},"AccessDenied",[506,6330,6331],{},"ErrorCode.AccessDenied",[506,6333,6334],{},"2002","), with a message that doesn't echo the credential",[521,6337,6338,6341],{},[539,6339,6340],{},"Contract",[539,6342,6343,6346,6347,6350],{},[506,6344,6345],{},"introspect"," returns the collections, operations, and relations you intend, for each configuration that changes them (e.g., ",[506,6348,6349],{},"enableWrites",")",[521,6352,6353,6356],{},[539,6354,6355],{},"Drift",[539,6357,6358],{},"Every declared field reads the value at its path in the external data source, on a live item",[521,6360,6361,6364],{},[539,6362,6363],{},"Reads",[539,6365,6366],{},"Pages, offsets, and merged results match the external data source's answer to the same request",[521,6368,6369,6372],{},[539,6370,6371],{},"Paging",[539,6373,6374,6375,6377],{},"A ",[506,6376,911],{}," above the external data source's page size returns its first page plus the next",[521,6379,6380,6383],{},[539,6381,6382],{},"Pushdown",[539,6384,6385,6386],{},"Each filter you translate reaches the external data source with the exact parameters. See ",[467,6387,6388],{"href":2482},"Assert What Goes on the Wire",[521,6390,6391,6394],{},[539,6392,6393],{},"Refusals",[539,6395,6396,6397,1153,6399,1181,6402,6404,6405,6408],{},"Each valid request you can't serve throws ",[506,6398,4389],{},[506,6400,6401],{},"ErrorCode.QueryRejected",[506,6403,4143],{},"), and each shape your collection doesn't declare throws a plain ",[506,6406,6407],{},"Error",", which carries no code. A refusal the request's shape decides sends no request; one that needs a probe (e.g., a count) sends only the probe",[521,6410,6411,6414],{},[539,6412,6413],{},"Keyed operations",[539,6415,6416,6417,6420,6421,6424,6425,6427],{},"Key alone and a matching guard (both answer ",[506,6418,6419],{},"{ record }","), a guard miss (answers ",[506,6422,6423],{},"{ record: null }"," and writes nothing), an unknown key, and a key your external data source can't have (both answer ",[506,6426,6423],{},", the last without a request)",[521,6429,6430,6433],{},[539,6431,6432],{},"Writes",[539,6434,6435],{},"Create, update, delete, cleaned up at the start of the next run, and the constraint errors you map. For an operation that makes several writes, a failure after the first one: the earlier writes stay applied, and your test checks what's left",[521,6437,6438,6441],{},[539,6439,6440],{},"Every declared operation",[539,6442,6443],{},"One request per declared operation of every collection, none refused",[521,6445,6446,6449],{},[539,6447,6448],{},"Answers",[539,6450,6451,6452,6454,6455,6457],{},"Every item carries every selected field, and a keyed operation answers ",[506,6453,6148],{},", never ",[506,6456,6082],{},". Engine checks answers only from rung 5 on",[458,6459,6460],{},"These checks from the Stripe smoke cover configuration, credentials, and keyed operations:",[938,6462,6464],{"className":940,"code":6463,"filename":761,"language":943,"meta":944,"style":944},"await check('setup validates the configuration with InvalidConfiguration, without a request', async () => {\n    assert.equal(await codeOf(() => descriptor.setup({ config: {} })), ErrorCode.InvalidConfiguration);\n    assert.equal(await codeOf(() => descriptor.setup({ config: { apiKey: 'pk_test_123' } })), ErrorCode.InvalidConfiguration);\n    assert.equal(await codeOf(() => descriptor.setup({ config: { apiKey, enableWrites: 'yes' } })), ErrorCode.InvalidConfiguration);\n    assert.equal(await codeOf(() => descriptor.setup({ config: { apiKey, maxItemsPerQuery: 0 } })), ErrorCode.InvalidConfiguration);\n    assert.equal(requests.length, 0);\n});\n\nawait check('a wrong key fails testConnection with AccessDenied, without echoing the key', async () => {\n    const wrong = await descriptor.setup({ config: { apiKey: 'sk_test_wrongwrongwrong' } });\n    const error = await wrong.testConnection().catch((thrown) => thrown);\n    assert.equal(error.monospace?.code, ErrorCode.AccessDenied);\n    assert.equal(error.message, 'Stripe rejected the API key (HTTP 401).');\n    assert.deepEqual(await connector.testConnection(), { ok: true });\n});\n\u002F\u002F …\nawait check('readOne: key, guard hit; a guard miss, an unknown key and an impossible id answer record null', async () => {\n    const one = async (key, filter) => await query({ op: 'readOne', collection: 'customers', select: ['id', 'name'], key, filter });\n    assert.deepEqual(await one({ id: ada.id }), { record: { id: ada.id, name: 'Ada Lovelace' } });\n    assert.deepEqual(await one({ id: ada.id }, compare('equals', 'email', fixtureEmail('ada'))), { record: { id: ada.id, name: 'Ada Lovelace' } });\n    assert.deepEqual(await one({ id: ada.id }, compare('equals', 'email', fixtureEmail('grace'))), { record: null });\n    assert.deepEqual(await one({ id: 'cus_doesnotexist' }), { record: null });\n    requests.length = 0;\n    assert.deepEqual(await one({ id: '..' }), { record: null });\n    assert.equal(requests.length, 0);\n});\n",[506,6465,6466,6487,6525,6565,6609,6652,6675,6679,6683,6704,6728,6763,6794,6818,6844,6848,6852,6873,6928,6966,7027,7076,7102,7116,7141,7163],{"__ignoreMap":944},[948,6467,6468,6470,6472,6474,6477,6479,6481,6483,6485],{"class":950,"line":951},[948,6469,5773],{"class":961},[948,6471,5907],{"class":1024},[948,6473,1176],{"class":965},[948,6475,6476],{"class":979},"'setup validates the configuration with InvalidConfiguration, without a request'",[948,6478,1181],{"class":965},[948,6480,3368],{"class":961},[948,6482,3327],{"class":965},[948,6484,1983],{"class":961},[948,6486,1986],{"class":965},[948,6488,6489,6492,6494,6497,6499,6501,6504,6506,6508,6510,6512,6514,6517,6519,6521,6523],{"class":950,"line":958},[948,6490,6491],{"class":969},"    assert",[948,6493,476],{"class":965},[948,6495,6496],{"class":1024},"equal",[948,6498,1176],{"class":965},[948,6500,5773],{"class":961},[948,6502,6503],{"class":1024}," codeOf",[948,6505,3304],{"class":965},[948,6507,1983],{"class":961},[948,6509,5872],{"class":969},[948,6511,476],{"class":965},[948,6513,3613],{"class":1024},[948,6515,6516],{"class":965},"({ config: {} })), ",[948,6518,5477],{"class":969},[948,6520,476],{"class":965},[948,6522,6306],{"class":969},[948,6524,1237],{"class":965},[948,6526,6527,6529,6531,6533,6535,6537,6539,6541,6543,6545,6547,6549,6551,6554,6557,6559,6561,6563],{"class":950,"line":986},[948,6528,6491],{"class":969},[948,6530,476],{"class":965},[948,6532,6496],{"class":1024},[948,6534,1176],{"class":965},[948,6536,5773],{"class":961},[948,6538,6503],{"class":1024},[948,6540,3304],{"class":965},[948,6542,1983],{"class":961},[948,6544,5872],{"class":969},[948,6546,476],{"class":965},[948,6548,3613],{"class":1024},[948,6550,3616],{"class":965},[948,6552,6553],{"class":979},"'pk_test_123'",[948,6555,6556],{"class":965}," } })), ",[948,6558,5477],{"class":969},[948,6560,476],{"class":965},[948,6562,6306],{"class":969},[948,6564,1237],{"class":965},[948,6566,6567,6569,6571,6573,6575,6577,6579,6581,6583,6585,6587,6589,6591,6593,6596,6599,6601,6603,6605,6607],{"class":950,"line":1002},[948,6568,6491],{"class":969},[948,6570,476],{"class":965},[948,6572,6496],{"class":1024},[948,6574,1176],{"class":965},[948,6576,5773],{"class":961},[948,6578,6503],{"class":1024},[948,6580,3304],{"class":965},[948,6582,1983],{"class":961},[948,6584,5872],{"class":969},[948,6586,476],{"class":965},[948,6588,3613],{"class":1024},[948,6590,5879],{"class":965},[948,6592,5546],{"class":969},[948,6594,6595],{"class":965},", enableWrites: ",[948,6597,6598],{"class":979},"'yes'",[948,6600,6556],{"class":965},[948,6602,5477],{"class":969},[948,6604,476],{"class":965},[948,6606,6306],{"class":969},[948,6608,1237],{"class":965},[948,6610,6611,6613,6615,6617,6619,6621,6623,6625,6627,6629,6631,6633,6635,6637,6640,6642,6644,6646,6648,6650],{"class":950,"line":1009},[948,6612,6491],{"class":969},[948,6614,476],{"class":965},[948,6616,6496],{"class":1024},[948,6618,1176],{"class":965},[948,6620,5773],{"class":961},[948,6622,6503],{"class":1024},[948,6624,3304],{"class":965},[948,6626,1983],{"class":961},[948,6628,5872],{"class":969},[948,6630,476],{"class":965},[948,6632,3613],{"class":1024},[948,6634,5879],{"class":965},[948,6636,5546],{"class":969},[948,6638,6639],{"class":965},", maxItemsPerQuery: ",[948,6641,3803],{"class":1024},[948,6643,6556],{"class":965},[948,6645,5477],{"class":969},[948,6647,476],{"class":965},[948,6649,6306],{"class":969},[948,6651,1237],{"class":965},[948,6653,6654,6656,6658,6660,6662,6665,6667,6669,6671,6673],{"class":950,"line":1015},[948,6655,6491],{"class":969},[948,6657,476],{"class":965},[948,6659,6496],{"class":1024},[948,6661,1176],{"class":965},[948,6663,6664],{"class":969},"requests",[948,6666,476],{"class":965},[948,6668,5927],{"class":2876},[948,6670,1181],{"class":965},[948,6672,3803],{"class":1024},[948,6674,1237],{"class":965},[948,6676,6677],{"class":950,"line":1036},[948,6678,3543],{"class":965},[948,6680,6681],{"class":950,"line":1053},[948,6682,1006],{"emptyLinePlaceholder":1005},[948,6684,6685,6687,6689,6691,6694,6696,6698,6700,6702],{"class":950,"line":1070},[948,6686,5773],{"class":961},[948,6688,5907],{"class":1024},[948,6690,1176],{"class":965},[948,6692,6693],{"class":979},"'a wrong key fails testConnection with AccessDenied, without echoing the key'",[948,6695,1181],{"class":965},[948,6697,3368],{"class":961},[948,6699,3327],{"class":965},[948,6701,1983],{"class":961},[948,6703,1986],{"class":965},[948,6705,6706,6708,6711,6713,6715,6717,6719,6721,6723,6726],{"class":950,"line":1075},[948,6707,1098],{"class":961},[948,6709,6710],{"class":1024}," wrong",[948,6712,1028],{"class":961},[948,6714,1778],{"class":961},[948,6716,5872],{"class":969},[948,6718,476],{"class":965},[948,6720,3613],{"class":1024},[948,6722,3616],{"class":965},[948,6724,6725],{"class":979},"'sk_test_wrongwrongwrong'",[948,6727,3629],{"class":965},[948,6729,6730,6732,6734,6736,6738,6740,6742,6744,6747,6749,6751,6754,6756,6758,6761],{"class":950,"line":1081},[948,6731,1098],{"class":961},[948,6733,2024],{"class":1024},[948,6735,1028],{"class":961},[948,6737,1778],{"class":961},[948,6739,6710],{"class":969},[948,6741,476],{"class":965},[948,6743,6324],{"class":1024},[948,6745,6746],{"class":965},"().",[948,6748,1972],{"class":1024},[948,6750,1975],{"class":965},[948,6752,6753],{"class":961},"thrown",[948,6755,1220],{"class":965},[948,6757,1983],{"class":961},[948,6759,6760],{"class":969}," thrown",[948,6762,1237],{"class":965},[948,6764,6765,6767,6769,6771,6773,6775,6777,6780,6782,6784,6786,6788,6790,6792],{"class":950,"line":1095},[948,6766,6491],{"class":969},[948,6768,476],{"class":965},[948,6770,6496],{"class":1024},[948,6772,1176],{"class":965},[948,6774,1978],{"class":969},[948,6776,476],{"class":965},[948,6778,6779],{"class":969},"monospace",[948,6781,1141],{"class":965},[948,6783,506],{"class":969},[948,6785,1181],{"class":965},[948,6787,5477],{"class":969},[948,6789,476],{"class":965},[948,6791,6328],{"class":969},[948,6793,1237],{"class":965},[948,6795,6796,6798,6800,6802,6804,6806,6808,6811,6813,6816],{"class":950,"line":1120},[948,6797,6491],{"class":969},[948,6799,476],{"class":965},[948,6801,6496],{"class":1024},[948,6803,1176],{"class":965},[948,6805,1978],{"class":969},[948,6807,476],{"class":965},[948,6809,6810],{"class":969},"message",[948,6812,1181],{"class":965},[948,6814,6815],{"class":979},"'Stripe rejected the API key (HTTP 401).'",[948,6817,1237],{"class":965},[948,6819,6820,6822,6824,6827,6829,6831,6833,6835,6837,6840,6842],{"class":950,"line":1195},[948,6821,6491],{"class":969},[948,6823,476],{"class":965},[948,6825,6826],{"class":1024},"deepEqual",[948,6828,1176],{"class":965},[948,6830,5773],{"class":961},[948,6832,3159],{"class":969},[948,6834,476],{"class":965},[948,6836,6324],{"class":1024},[948,6838,6839],{"class":965},"(), { ok: ",[948,6841,2429],{"class":1024},[948,6843,1805],{"class":965},[948,6845,6846],{"class":950,"line":1240},[948,6847,3543],{"class":965},[948,6849,6850],{"class":950,"line":1246},[948,6851,4669],{"class":954},[948,6853,6854,6856,6858,6860,6863,6865,6867,6869,6871],{"class":950,"line":1298},[948,6855,5773],{"class":961},[948,6857,5907],{"class":1024},[948,6859,1176],{"class":965},[948,6861,6862],{"class":979},"'readOne: key, guard hit; a guard miss, an unknown key and an impossible id answer record null'",[948,6864,1181],{"class":965},[948,6866,3368],{"class":961},[948,6868,3327],{"class":965},[948,6870,1983],{"class":961},[948,6872,1986],{"class":965},[948,6874,6875,6877,6880,6882,6884,6886,6888,6890,6892,6894,6896,6898,6900,6902,6905,6907,6909,6911,6913,6915,6918,6920,6922,6924,6926],{"class":950,"line":1308},[948,6876,1098],{"class":961},[948,6878,6879],{"class":1024}," one",[948,6881,1028],{"class":961},[948,6883,4059],{"class":961},[948,6885,1153],{"class":965},[948,6887,1203],{"class":961},[948,6889,1181],{"class":965},[948,6891,4367],{"class":961},[948,6893,1220],{"class":965},[948,6895,1983],{"class":961},[948,6897,1778],{"class":961},[948,6899,3646],{"class":1024},[948,6901,4348],{"class":965},[948,6903,6904],{"class":979},"'readOne'",[948,6906,4354],{"class":965},[948,6908,1790],{"class":979},[948,6910,4359],{"class":965},[948,6912,4313],{"class":979},[948,6914,1181],{"class":965},[948,6916,6917],{"class":979},"'name'",[948,6919,4364],{"class":965},[948,6921,1203],{"class":969},[948,6923,1181],{"class":965},[948,6925,4367],{"class":969},[948,6927,1805],{"class":965},[948,6929,6930,6932,6934,6936,6938,6940,6942,6944,6947,6949,6951,6954,6956,6958,6960,6962,6964],{"class":950,"line":1591},[948,6931,6491],{"class":969},[948,6933,476],{"class":965},[948,6935,6826],{"class":1024},[948,6937,1176],{"class":965},[948,6939,5773],{"class":961},[948,6941,6879],{"class":1024},[948,6943,4276],{"class":965},[948,6945,6946],{"class":969},"ada",[948,6948,476],{"class":965},[948,6950,1920],{"class":969},[948,6952,6953],{"class":965}," }), { record: { id: ",[948,6955,6946],{"class":969},[948,6957,476],{"class":965},[948,6959,1920],{"class":969},[948,6961,1440],{"class":965},[948,6963,1443],{"class":979},[948,6965,3629],{"class":965},[948,6967,6968,6970,6972,6974,6976,6978,6980,6982,6984,6986,6988,6991,6994,6996,6998,7000,7003,7005,7008,7010,7012,7015,7017,7019,7021,7023,7025],{"class":950,"line":1610},[948,6969,6491],{"class":969},[948,6971,476],{"class":965},[948,6973,6826],{"class":1024},[948,6975,1176],{"class":965},[948,6977,5773],{"class":961},[948,6979,6879],{"class":1024},[948,6981,4276],{"class":965},[948,6983,6946],{"class":969},[948,6985,476],{"class":965},[948,6987,1920],{"class":969},[948,6989,6990],{"class":965}," }, ",[948,6992,6993],{"class":1024},"compare",[948,6995,1176],{"class":965},[948,6997,4925],{"class":979},[948,6999,1181],{"class":965},[948,7001,7002],{"class":979},"'email'",[948,7004,1181],{"class":965},[948,7006,7007],{"class":1024},"fixtureEmail",[948,7009,1176],{"class":965},[948,7011,1437],{"class":979},[948,7013,7014],{"class":965},"))), { record: { id: ",[948,7016,6946],{"class":969},[948,7018,476],{"class":965},[948,7020,1920],{"class":969},[948,7022,1440],{"class":965},[948,7024,1443],{"class":979},[948,7026,3629],{"class":965},[948,7028,7029,7031,7033,7035,7037,7039,7041,7043,7045,7047,7049,7051,7053,7055,7057,7059,7061,7063,7065,7067,7069,7072,7074],{"class":950,"line":1625},[948,7030,6491],{"class":969},[948,7032,476],{"class":965},[948,7034,6826],{"class":1024},[948,7036,1176],{"class":965},[948,7038,5773],{"class":961},[948,7040,6879],{"class":1024},[948,7042,4276],{"class":965},[948,7044,6946],{"class":969},[948,7046,476],{"class":965},[948,7048,1920],{"class":969},[948,7050,6990],{"class":965},[948,7052,6993],{"class":1024},[948,7054,1176],{"class":965},[948,7056,4925],{"class":979},[948,7058,1181],{"class":965},[948,7060,7002],{"class":979},[948,7062,1181],{"class":965},[948,7064,7007],{"class":1024},[948,7066,1176],{"class":965},[948,7068,1465],{"class":979},[948,7070,7071],{"class":965},"))), { record: ",[948,7073,3789],{"class":1024},[948,7075,1805],{"class":965},[948,7077,7078,7080,7082,7084,7086,7088,7090,7092,7095,7098,7100],{"class":950,"line":1630},[948,7079,6491],{"class":969},[948,7081,476],{"class":965},[948,7083,6826],{"class":1024},[948,7085,1176],{"class":965},[948,7087,5773],{"class":961},[948,7089,6879],{"class":1024},[948,7091,4276],{"class":965},[948,7093,7094],{"class":979},"'cus_doesnotexist'",[948,7096,7097],{"class":965}," }), { record: ",[948,7099,3789],{"class":1024},[948,7101,1805],{"class":965},[948,7103,7104,7106,7108,7110,7112,7114],{"class":950,"line":1636},[948,7105,5627],{"class":969},[948,7107,476],{"class":965},[948,7109,5927],{"class":2876},[948,7111,1028],{"class":961},[948,7113,5932],{"class":1024},[948,7115,983],{"class":965},[948,7117,7118,7120,7122,7124,7126,7128,7130,7132,7135,7137,7139],{"class":950,"line":1656},[948,7119,6491],{"class":969},[948,7121,476],{"class":965},[948,7123,6826],{"class":1024},[948,7125,1176],{"class":965},[948,7127,5773],{"class":961},[948,7129,6879],{"class":1024},[948,7131,4276],{"class":965},[948,7133,7134],{"class":979},"'..'",[948,7136,7097],{"class":965},[948,7138,3789],{"class":1024},[948,7140,1805],{"class":965},[948,7142,7143,7145,7147,7149,7151,7153,7155,7157,7159,7161],{"class":950,"line":1671},[948,7144,6491],{"class":969},[948,7146,476],{"class":965},[948,7148,6496],{"class":1024},[948,7150,1176],{"class":965},[948,7152,6664],{"class":969},[948,7154,476],{"class":965},[948,7156,5927],{"class":2876},[948,7158,1181],{"class":965},[948,7160,3803],{"class":1024},[948,7162,1237],{"class":965},[948,7164,7165],{"class":950,"line":1676},[948,7166,3543],{"class":965},[458,7168,7169,7170,7172,7173,7176,7177,7180,7181,7183,7184,7186,7187,7189,7190,7193,7194,7197,7198,7201,7202,7204,7205,7207,7208,7210],{},"The helpers come from the top of the script: ",[506,7171,3704],{}," sends ",[506,7174,7175],{},"{ query: operation }"," to the handler, ",[506,7178,7179],{},"read"," and ",[506,7182,4319],{}," return what a ",[506,7185,4811],{}," answers, ",[506,7188,6993],{}," builds a comparison filter, and ",[506,7191,7192],{},"codeOf"," returns the ",[506,7195,7196],{},"monospace.code"," of whatever a call throws, or ",[506,7199,7200],{},"PLAIN_ERROR"," for a plain ",[506,7203,6407],{},". The checks compare with ",[506,7206,5477],{},", never a hard-coded number. ",[506,7209,6946],{}," is a fixture customer the script looks up before the first check.",[458,7212,7213,7214,7216],{},"The artifact smoke runs in Node, while Engine runs the connector in a sandboxed Deno worker. Network permissions, the timeouts on loading and ",[506,7215,3613],{},", and Engine's own requests exist only once Engine runs the connector, from rung 5 on.",[458,7218,7219,7220,7223],{},"When it works, the smoke prints ",[506,7221,7222],{},"{\"ok\":true,\"checks\":[…]}"," with every check named:",[938,7225,7228],{"className":7226,"code":7227,"filename":5256,"language":5257,"meta":944},[5254],"{\"ok\":true,\"checks\":[\"manifest declares exactly net:api.stripe.com and the three returning capabilities, and there is no preflight\",\"setup validates the configuration with InvalidConfiguration, without a request\",\"a wrong key fails testConnection with AccessDenied, without echoing the key\",\"every request carries the pinned API version and an abort signal\",\"drift: every declared field reads the value at its Stripe path\",\"the first page and an offset match Stripe\",\"limit 150 is a Stripe page of 100 and the next 50\",\"email in: one list request per value, merged newest first\",\"prices by productId match Stripe, with camelCase fields\",\"a filter on a product that does not exist answers no item\",\"created: a fraction of a second moves the bound to the next whole second\",\"readOne: key, guard hit; a guard miss, an unknown key and an impossible id answer record null\",\"refusals send no request: undeclared shapes are plain errors, the rest rejected\",\"an unbounded read beyond maxItemsPerQuery is refused\",\"writes are refused when enableWrites is false\",\"customer create, updateOne (a guard miss writes nothing and answers record null), deleteOne\",\"updateOne and deleteOne answer record null when the customer is deleted after the connector retrieved it\",\"a price for a product that does not exist is a ForeignKeyConstraintViolation\",\"deleting a product that has prices is a ForeignKeyConstraintViolation\"]}\n",[506,7229,7227],{"__ignoreMap":944},[2450,7231,6388],{"id":7232},"assert-what-goes-on-the-wire",[458,7234,7235],{},"A result check passes whenever the items are right. Two things make items right even when the request to the external data source is wrong: a fixture that ignores a parameter, and the connector's own re-check of returned items against the filter. The Stripe connector re-checks merged and retrieved results, not the answer of a single list request. Record the outgoing requests, and assert on them.",[458,7237,7238,7239,7241,7242,7245,7246,7248,7249,7251],{},"The Stripe smoke checks that both requests of an ",[506,7240,1796],{}," ",[506,7243,7244],{},"in"," read carry the pinned API version and an abort signal, and that a ",[506,7247,2434],{}," bound one microsecond past a customer's ",[506,7250,2434],{}," second reaches Stripe as the next whole second:",[938,7253,7255],{"className":940,"code":7254,"filename":761,"language":943,"meta":944,"style":944},"await check('every request carries the pinned API version and an abort signal', async () => {\n    await ids('customers', { filter: { in: { field: 'email' }, values: ['ada', 'grace'].map((key) => fixtureEmail(key)) } });\n    assert.equal(requests.length, 2);\n\n    for (const request of requests) {\n        assert.equal(request.version, STRIPE_API_VERSION);\n        assert.ok(request.signal instanceof AbortSignal);\n    }\n});\n\u002F\u002F …\nawait check('created: a fraction of a second moves the bound to the next whole second', async () => {\n    const created = new Date(ada.created * 1000).toISOString();\n    const justAfter = created.replace('.000Z', '.000001Z');\n    const byEmail = compare('equals', 'email', fixtureEmail('ada'));\n    assert.deepEqual(await ids('customers', { filter: { and: [byEmail, compare('greaterThanOrEquals', 'created', created)] } }), [ada.id]);\n    assert.deepEqual(await ids('customers', { filter: { and: [byEmail, compare('greaterThanOrEquals', 'created', justAfter)] } }), []);\n    assert.equal(new URL(requests.at(-1).url).searchParams.get('created[gte]'), String(ada.created + 1));\n    assert.deepEqual(await ids('customers', { filter: { and: [byEmail, compare('lessThan', 'created', justAfter)] } }), [ada.id]);\n});\n",[506,7256,7257,7278,7324,7347,7351,7368,7392,7416,7420,7424,7428,7449,7483,7510,7539,7591,7633,7701,7749],{"__ignoreMap":944},[948,7258,7259,7261,7263,7265,7268,7270,7272,7274,7276],{"class":950,"line":951},[948,7260,5773],{"class":961},[948,7262,5907],{"class":1024},[948,7264,1176],{"class":965},[948,7266,7267],{"class":979},"'every request carries the pinned API version and an abort signal'",[948,7269,1181],{"class":965},[948,7271,3368],{"class":961},[948,7273,3327],{"class":965},[948,7275,1983],{"class":961},[948,7277,1986],{"class":965},[948,7279,7280,7282,7284,7286,7288,7291,7293,7295,7297,7299,7301,7304,7306,7308,7310,7312,7314,7317,7319,7321],{"class":950,"line":958},[948,7281,1837],{"class":961},[948,7283,3938],{"class":1024},[948,7285,1176],{"class":965},[948,7287,1790],{"class":979},[948,7289,7290],{"class":965},", { filter: { in: { field: ",[948,7292,7002],{"class":979},[948,7294,5079],{"class":965},[948,7296,1437],{"class":979},[948,7298,1181],{"class":965},[948,7300,1465],{"class":979},[948,7302,7303],{"class":965},"].",[948,7305,4453],{"class":1024},[948,7307,1975],{"class":965},[948,7309,1203],{"class":961},[948,7311,1220],{"class":965},[948,7313,1983],{"class":961},[948,7315,7316],{"class":1024}," fixtureEmail",[948,7318,1176],{"class":965},[948,7320,1203],{"class":969},[948,7322,7323],{"class":965},")) } });\n",[948,7325,7326,7328,7330,7332,7334,7336,7338,7340,7342,7345],{"class":950,"line":986},[948,7327,6491],{"class":969},[948,7329,476],{"class":965},[948,7331,6496],{"class":1024},[948,7333,1176],{"class":965},[948,7335,6664],{"class":969},[948,7337,476],{"class":965},[948,7339,5927],{"class":2876},[948,7341,1181],{"class":965},[948,7343,7344],{"class":1024},"2",[948,7346,1237],{"class":965},[948,7348,7349],{"class":950,"line":1002},[948,7350,1006],{"emptyLinePlaceholder":1005},[948,7352,7353,7355,7357,7359,7361,7364,7366],{"class":950,"line":1009},[948,7354,2169],{"class":961},[948,7356,1153],{"class":965},[948,7358,1399],{"class":961},[948,7360,3411],{"class":1024},[948,7362,7363],{"class":961}," of",[948,7365,5555],{"class":969},[948,7367,1733],{"class":965},[948,7369,7370,7373,7375,7377,7379,7381,7383,7386,7388,7390],{"class":950,"line":1015},[948,7371,7372],{"class":969},"        assert",[948,7374,476],{"class":965},[948,7376,6496],{"class":1024},[948,7378,1176],{"class":965},[948,7380,3494],{"class":969},[948,7382,476],{"class":965},[948,7384,7385],{"class":969},"version",[948,7387,1181],{"class":965},[948,7389,5502],{"class":1024},[948,7391,1237],{"class":965},[948,7393,7394,7396,7398,7400,7402,7404,7406,7408,7411,7414],{"class":950,"line":1036},[948,7395,7372],{"class":969},[948,7397,476],{"class":965},[948,7399,5273],{"class":1024},[948,7401,1176],{"class":965},[948,7403,3494],{"class":969},[948,7405,476],{"class":965},[948,7407,5676],{"class":969},[948,7409,7410],{"class":961}," instanceof",[948,7412,7413],{"class":1024}," AbortSignal",[948,7415,1237],{"class":965},[948,7417,7418],{"class":950,"line":1053},[948,7419,2119],{"class":965},[948,7421,7422],{"class":950,"line":1070},[948,7423,3543],{"class":965},[948,7425,7426],{"class":950,"line":1075},[948,7427,4669],{"class":954},[948,7429,7430,7432,7434,7436,7439,7441,7443,7445,7447],{"class":950,"line":1081},[948,7431,5773],{"class":961},[948,7433,5907],{"class":1024},[948,7435,1176],{"class":965},[948,7437,7438],{"class":979},"'created: a fraction of a second moves the bound to the next whole second'",[948,7440,1181],{"class":965},[948,7442,3368],{"class":961},[948,7444,3327],{"class":965},[948,7446,1983],{"class":961},[948,7448,1986],{"class":965},[948,7450,7451,7453,7455,7457,7459,7462,7464,7466,7468,7470,7473,7476,7478,7481],{"class":950,"line":1095},[948,7452,1098],{"class":961},[948,7454,1691],{"class":1024},[948,7456,1028],{"class":961},[948,7458,1226],{"class":961},[948,7460,7461],{"class":1024}," Date",[948,7463,1176],{"class":965},[948,7465,6946],{"class":969},[948,7467,476],{"class":965},[948,7469,2434],{"class":969},[948,7471,7472],{"class":961}," *",[948,7474,7475],{"class":1024}," 1000",[948,7477,1187],{"class":965},[948,7479,7480],{"class":1024},"toISOString",[948,7482,3538],{"class":965},[948,7484,7485,7487,7490,7492,7494,7496,7498,7500,7503,7505,7508],{"class":950,"line":1120},[948,7486,1098],{"class":961},[948,7488,7489],{"class":1024}," justAfter",[948,7491,1028],{"class":961},[948,7493,1691],{"class":969},[948,7495,476],{"class":965},[948,7497,3440],{"class":1024},[948,7499,1176],{"class":965},[948,7501,7502],{"class":979},"'.000Z'",[948,7504,1181],{"class":965},[948,7506,7507],{"class":979},"'.000001Z'",[948,7509,1237],{"class":965},[948,7511,7512,7514,7517,7519,7521,7523,7525,7527,7529,7531,7533,7535,7537],{"class":950,"line":1195},[948,7513,1098],{"class":961},[948,7515,7516],{"class":1024}," byEmail",[948,7518,1028],{"class":961},[948,7520,6176],{"class":1024},[948,7522,1176],{"class":965},[948,7524,4925],{"class":979},[948,7526,1181],{"class":965},[948,7528,7002],{"class":979},[948,7530,1181],{"class":965},[948,7532,7007],{"class":1024},[948,7534,1176],{"class":965},[948,7536,1437],{"class":979},[948,7538,5806],{"class":965},[948,7540,7541,7543,7545,7547,7549,7551,7553,7555,7557,7560,7563,7565,7567,7569,7571,7573,7576,7578,7580,7583,7585,7587,7589],{"class":950,"line":1240},[948,7542,6491],{"class":969},[948,7544,476],{"class":965},[948,7546,6826],{"class":1024},[948,7548,1176],{"class":965},[948,7550,5773],{"class":961},[948,7552,3938],{"class":1024},[948,7554,1176],{"class":965},[948,7556,1790],{"class":979},[948,7558,7559],{"class":965},", { filter: { and: [",[948,7561,7562],{"class":969},"byEmail",[948,7564,1181],{"class":965},[948,7566,6993],{"class":1024},[948,7568,1176],{"class":965},[948,7570,4945],{"class":979},[948,7572,1181],{"class":965},[948,7574,7575],{"class":979},"'created'",[948,7577,1181],{"class":965},[948,7579,2434],{"class":969},[948,7581,7582],{"class":965},")] } }), [",[948,7584,6946],{"class":969},[948,7586,476],{"class":965},[948,7588,1920],{"class":969},[948,7590,4497],{"class":965},[948,7592,7593,7595,7597,7599,7601,7603,7605,7607,7609,7611,7613,7615,7617,7619,7621,7623,7625,7627,7630],{"class":950,"line":1246},[948,7594,6491],{"class":969},[948,7596,476],{"class":965},[948,7598,6826],{"class":1024},[948,7600,1176],{"class":965},[948,7602,5773],{"class":961},[948,7604,3938],{"class":1024},[948,7606,1176],{"class":965},[948,7608,1790],{"class":979},[948,7610,7559],{"class":965},[948,7612,7562],{"class":969},[948,7614,1181],{"class":965},[948,7616,6993],{"class":1024},[948,7618,1176],{"class":965},[948,7620,4945],{"class":979},[948,7622,1181],{"class":965},[948,7624,7575],{"class":979},[948,7626,1181],{"class":965},[948,7628,7629],{"class":969},"justAfter",[948,7631,7632],{"class":965},")] } }), []);\n",[948,7634,7635,7637,7639,7641,7643,7645,7647,7649,7651,7653,7656,7658,7661,7663,7665,7667,7669,7671,7673,7675,7677,7680,7682,7685,7687,7689,7691,7693,7696,7699],{"class":950,"line":1298},[948,7636,6491],{"class":969},[948,7638,476],{"class":965},[948,7640,6496],{"class":1024},[948,7642,1176],{"class":965},[948,7644,3466],{"class":961},[948,7646,3376],{"class":1024},[948,7648,1176],{"class":965},[948,7650,6664],{"class":969},[948,7652,476],{"class":965},[948,7654,7655],{"class":1024},"at",[948,7657,1176],{"class":965},[948,7659,7660],{"class":961},"-",[948,7662,4132],{"class":1024},[948,7664,1187],{"class":965},[948,7666,3430],{"class":969},[948,7668,1187],{"class":965},[948,7670,3460],{"class":969},[948,7672,476],{"class":965},[948,7674,1958],{"class":1024},[948,7676,1176],{"class":965},[948,7678,7679],{"class":979},"'created[gte]'",[948,7681,5128],{"class":965},[948,7683,7684],{"class":1024},"String",[948,7686,1176],{"class":965},[948,7688,6946],{"class":969},[948,7690,476],{"class":965},[948,7692,2434],{"class":969},[948,7694,7695],{"class":961}," +",[948,7697,7698],{"class":1024}," 1",[948,7700,5806],{"class":965},[948,7702,7703,7705,7707,7709,7711,7713,7715,7717,7719,7721,7723,7725,7727,7729,7731,7733,7735,7737,7739,7741,7743,7745,7747],{"class":950,"line":1308},[948,7704,6491],{"class":969},[948,7706,476],{"class":965},[948,7708,6826],{"class":1024},[948,7710,1176],{"class":965},[948,7712,5773],{"class":961},[948,7714,3938],{"class":1024},[948,7716,1176],{"class":965},[948,7718,1790],{"class":979},[948,7720,7559],{"class":965},[948,7722,7562],{"class":969},[948,7724,1181],{"class":965},[948,7726,6993],{"class":1024},[948,7728,1176],{"class":965},[948,7730,4930],{"class":979},[948,7732,1181],{"class":965},[948,7734,7575],{"class":979},[948,7736,1181],{"class":965},[948,7738,7629],{"class":969},[948,7740,7582],{"class":965},[948,7742,6946],{"class":969},[948,7744,476],{"class":965},[948,7746,1920],{"class":969},[948,7748,4497],{"class":965},[948,7750,7751],{"class":950,"line":1591},[948,7752,3543],{"class":965},[458,7754,7755,7756,7758,7759,7761,7762,7764,7765,7768],{},"Assert absence too. A refusal decided by the request's shape must send no request, and a result check can't tell. These Stripe refusals are all shape-based. The first three name shapes the collection doesn't declare, so they're plain errors. An extension update without a schema refresh can send an undeclared sort or count. A permission rule's condition can also carry an undeclared filter field; the ",[506,7757,3789],{}," filter is a valid request Stripe can't express, so it's ",[506,7760,4389],{},". The last two add top-level ids: the undeclared field inside a ",[506,7763,5135],{}," is still a plain error, and a malformed timestamp behind an ",[506,7766,7767],{},"or"," that an item without values already decides is still refused before the retrieve:",[938,7770,7772],{"className":940,"code":7771,"filename":761,"language":943,"meta":944,"style":944},"await check('refusals send no request: undeclared shapes are plain errors, the rest rejected', async () => {\n    assert.equal(await codeOf(() => ids('customers', { filter: compare('equals', 'name', 'Ada Lovelace') })), PLAIN_ERROR);\n    assert.equal(await codeOf(() => ids('customers', { sort: [{ field: 'created', direction: 'ascending' }] })), PLAIN_ERROR);\n    assert.equal(await codeOf(() => query({ op: 'readMany', collection: 'customers', count: true, limit: 0 })), PLAIN_ERROR);\n    assert.equal(await codeOf(() => ids('customers', { filter: compare('equals', 'email', null) })), ErrorCode.QueryRejected);\n    \u002F\u002F With top-level ids too: every comparison is checked, even one an `or` or `not` would skip.\n    const byId = compare('equals', 'id', ada.id);\n    assert.equal(await codeOf(() => ids('customers', { filter: { and: [byId, { not: compare('equals', 'name', 'x') }] } })), PLAIN_ERROR);\n    assert.equal(await codeOf(() => ids('customers', { filter: { and: [byId, { or: [compare('equals', 'email', null), compare('equals', 'created', 'bad-time')] }] } })), ErrorCode.QueryRejected);\n    assert.equal(requests.length, 0);\n});\n",[506,7773,7774,7795,7843,7885,7929,7979,7984,8013,8067,8140,8162],{"__ignoreMap":944},[948,7775,7776,7778,7780,7782,7785,7787,7789,7791,7793],{"class":950,"line":951},[948,7777,5773],{"class":961},[948,7779,5907],{"class":1024},[948,7781,1176],{"class":965},[948,7783,7784],{"class":979},"'refusals send no request: undeclared shapes are plain errors, the rest rejected'",[948,7786,1181],{"class":965},[948,7788,3368],{"class":961},[948,7790,3327],{"class":965},[948,7792,1983],{"class":961},[948,7794,1986],{"class":965},[948,7796,7797,7799,7801,7803,7805,7807,7809,7811,7813,7815,7817,7819,7822,7824,7826,7828,7830,7832,7834,7836,7839,7841],{"class":950,"line":958},[948,7798,6491],{"class":969},[948,7800,476],{"class":965},[948,7802,6496],{"class":1024},[948,7804,1176],{"class":965},[948,7806,5773],{"class":961},[948,7808,6503],{"class":1024},[948,7810,3304],{"class":965},[948,7812,1983],{"class":961},[948,7814,3938],{"class":1024},[948,7816,1176],{"class":965},[948,7818,1790],{"class":979},[948,7820,7821],{"class":965},", { filter: ",[948,7823,6993],{"class":1024},[948,7825,1176],{"class":965},[948,7827,4925],{"class":979},[948,7829,1181],{"class":965},[948,7831,6917],{"class":979},[948,7833,1181],{"class":965},[948,7835,1443],{"class":979},[948,7837,7838],{"class":965},") })), ",[948,7840,7200],{"class":1024},[948,7842,1237],{"class":965},[948,7844,7845,7847,7849,7851,7853,7855,7857,7859,7861,7863,7865,7867,7870,7872,7875,7878,7881,7883],{"class":950,"line":986},[948,7846,6491],{"class":969},[948,7848,476],{"class":965},[948,7850,6496],{"class":1024},[948,7852,1176],{"class":965},[948,7854,5773],{"class":961},[948,7856,6503],{"class":1024},[948,7858,3304],{"class":965},[948,7860,1983],{"class":961},[948,7862,3938],{"class":1024},[948,7864,1176],{"class":965},[948,7866,1790],{"class":979},[948,7868,7869],{"class":965},", { sort: [{ field: ",[948,7871,7575],{"class":979},[948,7873,7874],{"class":965},", direction: ",[948,7876,7877],{"class":979},"'ascending'",[948,7879,7880],{"class":965}," }] })), ",[948,7882,7200],{"class":1024},[948,7884,1237],{"class":965},[948,7886,7887,7889,7891,7893,7895,7897,7899,7901,7903,7905,7907,7909,7911,7913,7916,7918,7920,7922,7925,7927],{"class":950,"line":1002},[948,7888,6491],{"class":969},[948,7890,476],{"class":965},[948,7892,6496],{"class":1024},[948,7894,1176],{"class":965},[948,7896,5773],{"class":961},[948,7898,6503],{"class":1024},[948,7900,3304],{"class":965},[948,7902,1983],{"class":961},[948,7904,3646],{"class":1024},[948,7906,4348],{"class":965},[948,7908,6055],{"class":979},[948,7910,4354],{"class":965},[948,7912,1790],{"class":979},[948,7914,7915],{"class":965},", count: ",[948,7917,2429],{"class":1024},[948,7919,1799],{"class":965},[948,7921,3803],{"class":1024},[948,7923,7924],{"class":965}," })), ",[948,7926,7200],{"class":1024},[948,7928,1237],{"class":965},[948,7930,7931,7933,7935,7937,7939,7941,7943,7945,7947,7949,7951,7953,7955,7957,7959,7961,7963,7965,7967,7969,7971,7973,7975,7977],{"class":950,"line":1009},[948,7932,6491],{"class":969},[948,7934,476],{"class":965},[948,7936,6496],{"class":1024},[948,7938,1176],{"class":965},[948,7940,5773],{"class":961},[948,7942,6503],{"class":1024},[948,7944,3304],{"class":965},[948,7946,1983],{"class":961},[948,7948,3938],{"class":1024},[948,7950,1176],{"class":965},[948,7952,1790],{"class":979},[948,7954,7821],{"class":965},[948,7956,6993],{"class":1024},[948,7958,1176],{"class":965},[948,7960,4925],{"class":979},[948,7962,1181],{"class":965},[948,7964,7002],{"class":979},[948,7966,1181],{"class":965},[948,7968,3789],{"class":1024},[948,7970,7838],{"class":965},[948,7972,5477],{"class":969},[948,7974,476],{"class":965},[948,7976,4389],{"class":969},[948,7978,1237],{"class":965},[948,7980,7981],{"class":950,"line":1015},[948,7982,7983],{"class":954},"    \u002F\u002F With top-level ids too: every comparison is checked, even one an `or` or `not` would skip.\n",[948,7985,7986,7988,7991,7993,7995,7997,7999,8001,8003,8005,8007,8009,8011],{"class":950,"line":1036},[948,7987,1098],{"class":961},[948,7989,7990],{"class":1024}," byId",[948,7992,1028],{"class":961},[948,7994,6176],{"class":1024},[948,7996,1176],{"class":965},[948,7998,4925],{"class":979},[948,8000,1181],{"class":965},[948,8002,4313],{"class":979},[948,8004,1181],{"class":965},[948,8006,6946],{"class":969},[948,8008,476],{"class":965},[948,8010,1920],{"class":969},[948,8012,1237],{"class":965},[948,8014,8015,8017,8019,8021,8023,8025,8027,8029,8031,8033,8035,8037,8039,8042,8045,8047,8049,8051,8053,8055,8057,8060,8063,8065],{"class":950,"line":1053},[948,8016,6491],{"class":969},[948,8018,476],{"class":965},[948,8020,6496],{"class":1024},[948,8022,1176],{"class":965},[948,8024,5773],{"class":961},[948,8026,6503],{"class":1024},[948,8028,3304],{"class":965},[948,8030,1983],{"class":961},[948,8032,3938],{"class":1024},[948,8034,1176],{"class":965},[948,8036,1790],{"class":979},[948,8038,7559],{"class":965},[948,8040,8041],{"class":969},"byId",[948,8043,8044],{"class":965},", { not: ",[948,8046,6993],{"class":1024},[948,8048,1176],{"class":965},[948,8050,4925],{"class":979},[948,8052,1181],{"class":965},[948,8054,6917],{"class":979},[948,8056,1181],{"class":965},[948,8058,8059],{"class":979},"'x'",[948,8061,8062],{"class":965},") }] } })), ",[948,8064,7200],{"class":1024},[948,8066,1237],{"class":965},[948,8068,8069,8071,8073,8075,8077,8079,8081,8083,8085,8087,8089,8091,8093,8095,8098,8100,8102,8104,8106,8108,8110,8112,8114,8116,8118,8120,8122,8124,8126,8129,8132,8134,8136,8138],{"class":950,"line":1070},[948,8070,6491],{"class":969},[948,8072,476],{"class":965},[948,8074,6496],{"class":1024},[948,8076,1176],{"class":965},[948,8078,5773],{"class":961},[948,8080,6503],{"class":1024},[948,8082,3304],{"class":965},[948,8084,1983],{"class":961},[948,8086,3938],{"class":1024},[948,8088,1176],{"class":965},[948,8090,1790],{"class":979},[948,8092,7559],{"class":965},[948,8094,8041],{"class":969},[948,8096,8097],{"class":965},", { or: [",[948,8099,6993],{"class":1024},[948,8101,1176],{"class":965},[948,8103,4925],{"class":979},[948,8105,1181],{"class":965},[948,8107,7002],{"class":979},[948,8109,1181],{"class":965},[948,8111,3789],{"class":1024},[948,8113,5128],{"class":965},[948,8115,6993],{"class":1024},[948,8117,1176],{"class":965},[948,8119,4925],{"class":979},[948,8121,1181],{"class":965},[948,8123,7575],{"class":979},[948,8125,1181],{"class":965},[948,8127,8128],{"class":979},"'bad-time'",[948,8130,8131],{"class":965},")] }] } })), ",[948,8133,5477],{"class":969},[948,8135,476],{"class":965},[948,8137,4389],{"class":969},[948,8139,1237],{"class":965},[948,8141,8142,8144,8146,8148,8150,8152,8154,8156,8158,8160],{"class":950,"line":1075},[948,8143,6491],{"class":969},[948,8145,476],{"class":965},[948,8147,6496],{"class":1024},[948,8149,1176],{"class":965},[948,8151,6664],{"class":969},[948,8153,476],{"class":965},[948,8155,5927],{"class":2876},[948,8157,1181],{"class":965},[948,8159,3803],{"class":1024},[948,8161,1237],{"class":965},[948,8163,8164],{"class":950,"line":1081},[948,8165,3543],{"class":965},[458,8167,8168,8169,8171],{},"A wrapped ",[506,8170,865],{}," sees only requests made in your Node process. It can't see requests the connector makes inside Engine. For that, put a recording HTTP proxy or a stand-in in front of the external data source.",[458,8173,8174],{},"The blind spot: you write the expected parameters, so a wrong belief about the external data source passes. Pin each fact about it you rely on with a live probe.",[2450,8176,8178],{"id":8177},"compare-with-the-external-data-source","Compare with the External Data Source",[458,8180,8181,8182,2322],{},"Ask the external data source directly, through your independent client, and compare. This proves paging and order without hard-coded totals. The check needs more than 100 customers in the account for a second page, and at least 150 to fill the whole ",[506,8183,911],{},[938,8185,8187],{"className":940,"code":8186,"filename":761,"language":943,"meta":944,"style":944},"await check('limit 150 is a Stripe page of 100 and the next 50', async () => {\n    const first = await stripe.list('customers', { limit: '100' });\n    const next = await stripe.list('customers', { limit: '50', starting_after: first.at(-1).id });\n    requests.length = 0;\n    assert.deepEqual(await ids('customers', { limit: 150 }), [...first, ...next].map((customer) => customer.id));\n    assert.equal(requests.length, 2);\n});\n",[506,8188,8189,8210,8238,8286,8300,8357,8379],{"__ignoreMap":944},[948,8190,8191,8193,8195,8197,8200,8202,8204,8206,8208],{"class":950,"line":951},[948,8192,5773],{"class":961},[948,8194,5907],{"class":1024},[948,8196,1176],{"class":965},[948,8198,8199],{"class":979},"'limit 150 is a Stripe page of 100 and the next 50'",[948,8201,1181],{"class":965},[948,8203,3368],{"class":961},[948,8205,3327],{"class":965},[948,8207,1983],{"class":961},[948,8209,1986],{"class":965},[948,8211,8212,8214,8217,8219,8221,8223,8225,8227,8229,8231,8234,8236],{"class":950,"line":958},[948,8213,1098],{"class":961},[948,8215,8216],{"class":1024}," first",[948,8218,1028],{"class":961},[948,8220,1778],{"class":961},[948,8222,1402],{"class":969},[948,8224,476],{"class":965},[948,8226,1785],{"class":1024},[948,8228,1176],{"class":965},[948,8230,1790],{"class":979},[948,8232,8233],{"class":965},", { limit: ",[948,8235,1666],{"class":979},[948,8237,1805],{"class":965},[948,8239,8240,8242,8245,8247,8249,8251,8253,8255,8257,8259,8261,8264,8267,8270,8272,8274,8276,8278,8280,8282,8284],{"class":950,"line":986},[948,8241,1098],{"class":961},[948,8243,8244],{"class":1024}," next",[948,8246,1028],{"class":961},[948,8248,1778],{"class":961},[948,8250,1402],{"class":969},[948,8252,476],{"class":965},[948,8254,1785],{"class":1024},[948,8256,1176],{"class":965},[948,8258,1790],{"class":979},[948,8260,8233],{"class":965},[948,8262,8263],{"class":979},"'50'",[948,8265,8266],{"class":965},", starting_after: ",[948,8268,8269],{"class":969},"first",[948,8271,476],{"class":965},[948,8273,7655],{"class":1024},[948,8275,1176],{"class":965},[948,8277,7660],{"class":961},[948,8279,4132],{"class":1024},[948,8281,1187],{"class":965},[948,8283,1920],{"class":969},[948,8285,1805],{"class":965},[948,8287,8288,8290,8292,8294,8296,8298],{"class":950,"line":1002},[948,8289,5627],{"class":969},[948,8291,476],{"class":965},[948,8293,5927],{"class":2876},[948,8295,1028],{"class":961},[948,8297,5932],{"class":1024},[948,8299,983],{"class":965},[948,8301,8302,8304,8306,8308,8310,8312,8314,8316,8318,8320,8323,8326,8328,8330,8332,8334,8337,8339,8341,8343,8345,8347,8349,8351,8353,8355],{"class":950,"line":1009},[948,8303,6491],{"class":969},[948,8305,476],{"class":965},[948,8307,6826],{"class":1024},[948,8309,1176],{"class":965},[948,8311,5773],{"class":961},[948,8313,3938],{"class":1024},[948,8315,1176],{"class":965},[948,8317,1790],{"class":979},[948,8319,8233],{"class":965},[948,8321,8322],{"class":1024},"150",[948,8324,8325],{"class":965}," }), [",[948,8327,1720],{"class":961},[948,8329,8269],{"class":969},[948,8331,1181],{"class":965},[948,8333,1720],{"class":961},[948,8335,8336],{"class":969},"next",[948,8338,7303],{"class":965},[948,8340,4453],{"class":1024},[948,8342,1975],{"class":965},[948,8344,4107],{"class":961},[948,8346,1220],{"class":965},[948,8348,1983],{"class":961},[948,8350,3728],{"class":969},[948,8352,476],{"class":965},[948,8354,1920],{"class":969},[948,8356,5806],{"class":965},[948,8358,8359,8361,8363,8365,8367,8369,8371,8373,8375,8377],{"class":950,"line":1015},[948,8360,6491],{"class":969},[948,8362,476],{"class":965},[948,8364,6496],{"class":1024},[948,8366,1176],{"class":965},[948,8368,6664],{"class":969},[948,8370,476],{"class":965},[948,8372,5927],{"class":2876},[948,8374,1181],{"class":965},[948,8376,7344],{"class":1024},[948,8378,1237],{"class":965},[948,8380,8381],{"class":950,"line":1036},[948,8382,3543],{"class":965},[458,8384,8385],{},"A comparison proves nothing about a filter the fixture ignores: the external data source's answer is then equally wrong. Pair it with wire assertions.",[2450,8387,8389],{"id":8388},"check-null-handling","Check Null Handling",[458,8391,8392,8393,8395,8396,8399,8400,8402,8403,8406,8407,8409,8410,8412,8413,8416,8417,8419],{},"Engine sends filters your callers didn't type, such as ",[506,8394,5135],{}," around an update's field rules. Where a nullable field declares ",[506,8397,8398],{},"equals"," and its filter declares ",[506,8401,5135],{},", a caller's ",[506,8404,8405],{},"_null: true"," arrives as ",[506,8408,8398],{}," with ",[506,8411,3789],{},", and ",[506,8414,8415],{},"_null: false"," as ",[506,8418,5135],{}," around it. Keep at least one fixture item with nulls in filterable fields, and check:",[884,8421,8422,8428],{},[887,8423,8424,8427],{},[506,8425,8426],{},"field = null"," matches only items where the field is null.",[887,8429,8430,8433,8434,8436],{},[506,8431,8432],{},"not (field = x)"," doesn't match an item where ",[506,8435,4837],{}," is null. Under SQL's three-valued logic, that comparison is unknown, not true.",[458,8438,8439,8440,8442,8443,8445,8446,476],{},"The Stripe connector's ",[506,8441,2957],{}," checks both against an item with a null ",[506,8444,1796],{},". Your connector defines its own null handling, so test the rules it implements. See ",[467,8447,8449],{"href":8448},"\u002Freference\u002Fextensions\u002Fdata-connectors\u002Fapi#handle-requests-you-didnt-declare","Handle Requests You Didn't Declare",[454,8451,8453],{"id":8452},"_4-install-and-check-the-catalog","4. Install and Check the Catalog",[458,8455,8456,8457,8460],{},"Install the build into your ",[462,8458,8459],{},"instance"," (your running Monospace deployment), then confirm that the instance lists your connector. A bundle can load without contributing a connector, for example when its entry id is already taken. Only a warning in the Engine log reports it.",[458,8462,8463,8464,509,8466,8468],{},"A listed entry proves the instance registered the entry from a valid manifest. It doesn't prove that the connector file loads, or that ",[506,8465,3613],{},[506,8467,6345],{}," works; rung 5 checks those.",[2496,8470,8471,8494,8509,8524],{"sync":2498},[938,8472,8474],{"className":2501,"code":8473,"filename":2503,"language":2504,"meta":944,"style":944},"npm run build -- --install-to ..\u002Fmonospace\u002Fextensions\n",[506,8475,8476],{"__ignoreMap":944},[948,8477,8478,8480,8482,8485,8488,8491],{"class":950,"line":951},[948,8479,2503],{"class":1024},[948,8481,2513],{"class":979},[948,8483,8484],{"class":979}," build",[948,8486,8487],{"class":1024}," --",[948,8489,8490],{"class":1024}," --install-to",[948,8492,8493],{"class":979}," ..\u002Fmonospace\u002Fextensions\n",[938,8495,8497],{"className":2501,"code":8496,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm build --install-to ..\u002Fmonospace\u002Fextensions\n",[506,8498,8499],{"__ignoreMap":944},[948,8500,8501,8503,8505,8507],{"class":950,"line":951},[948,8502,2520],{"class":1024},[948,8504,8484],{"class":979},[948,8506,8490],{"class":1024},[948,8508,8493],{"class":979},[938,8510,8512],{"className":2501,"code":8511,"filename":2532,"language":2504,"meta":944,"style":944},"yarn build --install-to ..\u002Fmonospace\u002Fextensions\n",[506,8513,8514],{"__ignoreMap":944},[948,8515,8516,8518,8520,8522],{"class":950,"line":951},[948,8517,2532],{"class":1024},[948,8519,8484],{"class":979},[948,8521,8490],{"class":1024},[948,8523,8493],{"class":979},[938,8525,8527],{"className":2501,"code":8526,"filename":2544,"language":2504,"meta":944,"style":944},"bun run build --install-to ..\u002Fmonospace\u002Fextensions\n",[506,8528,8529],{"__ignoreMap":944},[948,8530,8531,8533,8535,8537,8539],{"class":950,"line":951},[948,8532,2544],{"class":1024},[948,8534,2513],{"class":979},[948,8536,8484],{"class":979},[948,8538,8490],{"class":1024},[948,8540,8493],{"class":979},[458,8542,5262,8543,8548,8549,476],{},[8544,8545],"env-var",{"name":8546,"section":8547},"extensions__auto_reload","extensions"," on, the instance reloads the extension once its files have been quiet for 500 ms. Otherwise, restart it. See ",[467,8550,227],{"href":8551},"\u002Fdeveloper\u002Fextensions\u002Finstall",[458,8553,8554,8555,2322],{},"Then list the workspace's data connectors with ",[506,8556,8557],{},"GET \u002Fapi\u002F{workspace}\u002Fconnectors\u002Fdata",[938,8559,8563],{"className":8560,"code":8561,"filename":8562,"language":3336,"meta":944,"style":944},"language-json shiki shiki-themes monospace-light monospace-light monospace-dark","{\n  \"data\": [\n    {\n      \"id\": \"example\u002Fstripe-connector\",\n      \"version\": \"0.1.0\",\n      \"name\": \"Stripe\",\n      \"icon\": \"simple-icons:stripe\",\n      \"extensionId\": \"example\u002Fstripe\",\n      \"permissions\": [\n        {\n          \"permission\": \"net:api.stripe.com\",\n          \"reason\": \"Read and write customers, products and prices through the Stripe API\"\n        }\n      ]\n    }\n  ]\n}\n","response.json",[506,8564,8565,8570,8578,8582,8594,8606,8618,8630,8642,8649,8654,8666,8676,8681,8686,8690,8695],{"__ignoreMap":944},[948,8566,8567],{"class":950,"line":951},[948,8568,8569],{"class":965},"{\n",[948,8571,8572,8575],{"class":950,"line":958},[948,8573,8574],{"class":2876},"  \"data\"",[948,8576,8577],{"class":965},": [\n",[948,8579,8580],{"class":950,"line":986},[948,8581,2778],{"class":965},[948,8583,8584,8587,8589,8592],{"class":950,"line":1002},[948,8585,8586],{"class":2876},"      \"id\"",[948,8588,1866],{"class":965},[948,8590,8591],{"class":979},"\"example\u002Fstripe-connector\"",[948,8593,2273],{"class":965},[948,8595,8596,8599,8601,8604],{"class":950,"line":1009},[948,8597,8598],{"class":2876},"      \"version\"",[948,8600,1866],{"class":965},[948,8602,8603],{"class":979},"\"0.1.0\"",[948,8605,2273],{"class":965},[948,8607,8608,8611,8613,8616],{"class":950,"line":1015},[948,8609,8610],{"class":2876},"      \"name\"",[948,8612,1866],{"class":965},[948,8614,8615],{"class":979},"\"Stripe\"",[948,8617,2273],{"class":965},[948,8619,8620,8623,8625,8628],{"class":950,"line":1036},[948,8621,8622],{"class":2876},"      \"icon\"",[948,8624,1866],{"class":965},[948,8626,8627],{"class":979},"\"simple-icons:stripe\"",[948,8629,2273],{"class":965},[948,8631,8632,8635,8637,8640],{"class":950,"line":1053},[948,8633,8634],{"class":2876},"      \"extensionId\"",[948,8636,1866],{"class":965},[948,8638,8639],{"class":979},"\"example\u002Fstripe\"",[948,8641,2273],{"class":965},[948,8643,8644,8647],{"class":950,"line":1070},[948,8645,8646],{"class":2876},"      \"permissions\"",[948,8648,8577],{"class":965},[948,8650,8651],{"class":950,"line":1075},[948,8652,8653],{"class":965},"        {\n",[948,8655,8656,8659,8661,8664],{"class":950,"line":1081},[948,8657,8658],{"class":2876},"          \"permission\"",[948,8660,1866],{"class":965},[948,8662,8663],{"class":979},"\"net:api.stripe.com\"",[948,8665,2273],{"class":965},[948,8667,8668,8671,8673],{"class":950,"line":1095},[948,8669,8670],{"class":2876},"          \"reason\"",[948,8672,1866],{"class":965},[948,8674,8675],{"class":979},"\"Read and write customers, products and prices through the Stripe API\"\n",[948,8677,8678],{"class":950,"line":1120},[948,8679,8680],{"class":965},"        }\n",[948,8682,8683],{"class":950,"line":1195},[948,8684,8685],{"class":965},"      ]\n",[948,8687,8688],{"class":950,"line":1240},[948,8689,2119],{"class":965},[948,8691,8692],{"class":950,"line":1246},[948,8693,8694],{"class":965},"  ]\n",[948,8696,8697],{"class":950,"line":1298},[948,8698,1311],{"class":965},[458,8700,8701,8702,8705],{},"In scripts, poll until your entry id appears with your ",[506,8703,8704],{},"extensionId",", instead of sleeping for a fixed time. The catalog reports the version from your manifest, so it changes only when you bump the version. An entry that's already listed can still be the previous build: bump the version, or check behavior the rebuild changed, to confirm the new build is active.",[458,8707,8708],{},"When it works, the catalog lists your entry id, with the version and permissions from your manifest.",[454,8710,8712],{"id":8711},"_5-run-an-engine-smoke","5. Run an Engine Smoke",[458,8714,8715],{},"Drive the installed connector through Engine's REST API. This is the only rung that runs Engine's declaration rules, the permission handshake, the sandbox, and the requests Engine composes on its own.",[458,8717,8718,8719,476],{},"Run it in a fresh data source every time. A reload swaps a data source's code but keeps the schema it was created with. Creating a data source checks your current declarations, and so does reintrospecting one. The example scripts create a throwaway workspace per run and delete it at the end, which also avoids name collisions such as ",[506,8720,8721],{},"customers_2",[458,8723,8724,8725,8728,8729,8733],{},"Send ",[506,8726,8727],{},"Cache-Control: no-cache"," on every read in your own smoke. Otherwise a repeated read can come from the ",[467,8730,8732],{"href":8731},"\u002Fconcepts\u002Fcaching#bypass-caching","query-result cache"," without reaching your connector, and pass when the connector would fail.",[458,8735,8736,8737,7180,8740,8743],{},"The example scripts need ",[506,8738,8739],{},"curl",[506,8741,8742],{},"jq",", and read these settings from the environment:",[515,8745,8746,8759],{},[518,8747,8748],{},[521,8749,8750,8753,8756],{},[524,8751,8752],{},"Variable",[524,8754,8755],{},"Default",[524,8757,8758],{},"Meaning",[534,8760,8761,8776,8796],{},[521,8762,8763,8768,8773],{},[539,8764,8765],{},[506,8766,8767],{},"ENGINE_URL",[539,8769,8770],{},[506,8771,8772],{},"http:\u002F\u002Flocalhost:8100",[539,8774,8775],{},"The instance to test",[521,8777,8778,8786,8793],{},[539,8779,8780,1181,8783],{},[506,8781,8782],{},"ENGINE_EMAIL",[506,8784,8785],{},"ENGINE_PASSWORD",[539,8787,8788,1181,8791],{},[506,8789,8790],{},"monospace@example.com",[506,8792,6779],{},[539,8794,8795],{},"A user allowed to create and delete workspaces on a development instance",[521,8797,8798,8804,8807],{},[539,8799,8800,509,8802],{},[506,8801,1138],{},[506,8803,1115],{},[539,8805,8806],{},"(required, Stripe only)",[539,8808,8809],{},"A Stripe test-mode key, or a file that holds only the key. This script drops only trailing newlines from the file and refuses a key with leading whitespace; the Node scripts trim surrounding whitespace",[2496,8811,8812,8826,8837,8848],{"sync":2498},[938,8813,8815],{"className":2501,"code":8814,"filename":2503,"language":2504,"meta":944,"style":944},"npm run smoke:engine\n",[506,8816,8817],{"__ignoreMap":944},[948,8818,8819,8821,8823],{"class":950,"line":951},[948,8820,2503],{"class":1024},[948,8822,2513],{"class":979},[948,8824,8825],{"class":979}," smoke:engine\n",[938,8827,8829],{"className":2501,"code":8828,"filename":2520,"language":2504,"meta":944,"style":944},"pnpm smoke:engine\n",[506,8830,8831],{"__ignoreMap":944},[948,8832,8833,8835],{"class":950,"line":951},[948,8834,2520],{"class":1024},[948,8836,8825],{"class":979},[938,8838,8840],{"className":2501,"code":8839,"filename":2532,"language":2504,"meta":944,"style":944},"yarn smoke:engine\n",[506,8841,8842],{"__ignoreMap":944},[948,8843,8844,8846],{"class":950,"line":951},[948,8845,2532],{"class":1024},[948,8847,8825],{"class":979},[938,8849,8851],{"className":2501,"code":8850,"filename":2544,"language":2504,"meta":944,"style":944},"bun run smoke:engine\n",[506,8852,8853],{"__ignoreMap":944},[948,8854,8855,8857,8859],{"class":950,"line":951},[948,8856,2544],{"class":1024},[948,8858,2513],{"class":979},[948,8860,8825],{"class":979},[458,8862,8863,8864,8867,8868,8870,8871,8873,8874,8877],{},"A shared library signs in, sends each request, and checks it. ",[506,8865,8866],{},"req"," sends one request and prints it with the HTTP status and the response. ",[506,8869,3133],{}," checks the last status, then compares each ",[506,8872,8742],{}," expression's result with an expected JSON value, and stops the run at the first mismatch. A ",[506,8875,8876],{},"trap"," tries to delete every throwaway workspace when the script exits, even after a failed check. That cleanup is best effort: it ignores failures. A successful run also deletes its workspaces as checked steps:",[1333,8879,8880],{},[1336,8881,8883],{"label":8882},"scripts\u002Fengine-lib.sh",[938,8884,8886],{"className":2501,"code":8885,"filename":8882,"language":2504,"meta":944,"style":944},"# Shared by the Engine smoke scripts. Sends requests to a running Engine, prints each request and\n# response as Markdown (the access token as $TOKEN), and stops the run at the first failed check.\n# Needs curl and jq. Settings: ENGINE_URL, ENGINE_EMAIL, ENGINE_PASSWORD.\nset -euo pipefail\n\nENGINE_URL=${ENGINE_URL:-http:\u002F\u002Flocalhost:8100}\nREDACT=${REDACT:-}\nCHECKS=0\nWORKSPACES=()\n\nTOKEN=$(curl -sf -X POST \"$ENGINE_URL\u002Fapi\u002Fauth\u002Fproviders\u002Flocal\u002Fpassword\u002Flogin\" \\\n    -H 'Content-Type: application\u002Fjson' \\\n    -d \"{\\\"email\\\":\\\"${ENGINE_EMAIL:-monospace@example.com}\\\",\\\"password\\\":\\\"${ENGINE_PASSWORD:-monospace}\\\",\\\"mode\\\":\\\"json\\\"}\" |\n    jq -r .accessToken)\n\nsay() { printf '\\n%s\\n' \"$*\"; }\n\nfail() {\n    printf '\\nFAIL: %s\\n' \"$*\" >&2\n    exit 1\n}\n\n# Replaces the secret in REDACT, when set, so it never reaches the output.\nredact() {\n    if [ -n \"$REDACT\" ]; then printf '%s' \"${1\u002F\u002F\"$REDACT\"\u002Fsk_test_YOUR_KEY}\"; else printf '%s' \"$1\"; fi\n}\n\n# req METHOD PATH [BODY] [JQ_FILTER]: sends one request, prints it and the (filtered) response.\n# `Cache-Control: no-cache` makes Engine run every read, so no check passes on a cached result.\nreq() {\n    local method=$1 path=$2 body=${3:-} filter=${4:-.} out\n    printf '\\n```bash\\ncurl -g -X %s \"%s%s\" \\\\\\n  -H \"Authorization: Bearer $TOKEN\" \\\\\\n  -H \"Cache-Control: no-cache\"' \"$method\" \"$ENGINE_URL\" \"$path\"\n    if [ -n \"$body\" ]; then\n        printf ' \\\\\\n  -H \"Content-Type: application\u002Fjson\" \\\\\\n  -d '\"'\"'%s'\"'\"'' \"$(redact \"$body\")\"\n        out=$(curl -sg -X \"$method\" \"$ENGINE_URL$path\" -H \"Authorization: Bearer $TOKEN\" -H 'Cache-Control: no-cache' -H 'Content-Type: application\u002Fjson' -d \"$body\" -w '\\n%{http_code}')\n    else\n        out=$(curl -sg -X \"$method\" \"$ENGINE_URL$path\" -H \"Authorization: Bearer $TOKEN\" -H 'Cache-Control: no-cache' -w '\\n%{http_code}')\n    fi\n    LAST_CODE=$(printf '%s' \"$out\" | tail -n 1)\n    LAST_BODY=$(redact \"$(printf '%s' \"$out\" | sed '$d')\")\n    printf '\\n```\\n\\n```json\\n\u002F\u002F HTTP %s\\n' \"$LAST_CODE\"\n    if [ -z \"$LAST_BODY\" ]; then echo '\u002F\u002F (empty body)'; else printf '%s' \"$LAST_BODY\" | jq \"$filter\" 2>\u002Fdev\u002Fnull || printf '%s\\n' \"$LAST_BODY\"; fi\n    printf '```\\n'\n}\n\n# expect STATUS [JQ_EXPRESSION EXPECTED_JSON]...: checks the last response's status, then each\n# expression's compact JSON result against the expected value.\nexpect() {\n    local status=$1 checked actual\n    shift\n    [ \"$LAST_CODE\" = \"$status\" ] || fail \"expected HTTP $status, got $LAST_CODE: $LAST_BODY\"\n    checked=\"HTTP $status\"\n    while [ $# -ge 2 ]; do\n        actual=$(printf '%s' \"$LAST_BODY\" | jq -c \"$1\")\n        [ \"$actual\" = \"$2\" ] || fail \"expected \\`$1\\` to be $2, got $actual\"\n        checked=\"$checked; \\`$1\\` is \\`$2\\`\"\n        shift 2\n    done\n    CHECKS=$((CHECKS + 1))\n    printf '\\nChecked: %s\\n' \"$checked\"\n}\n\n# workspace NAME: creates a throwaway workspace that is deleted when the script exits, even on failure.\nworkspace() {\n    WORKSPACES+=(\"$1\")\n    req POST \u002Fapi\u002Fsystem\u002Fworkspaces \"{\\\"apiName\\\":\\\"$1\\\"}\"\n    expect 200 '.data.id | type' '\"string\"'\n}\n\ncleanup() {\n    local name\n    # The `+` form keeps an empty array from failing `set -u` in older bash.\n    for name in ${WORKSPACES[@]+\"${WORKSPACES[@]}\"}; do\n        curl -sg -o \u002Fdev\u002Fnull -X DELETE \"$ENGINE_URL\u002Fapi\u002Fsystem\u002Fworkspaces\u002F$name?fields=id\" -H \"Authorization: Bearer $TOKEN\" || true\n    done\n}\ntrap cleanup EXIT\n\nfinish() {\n    say \"All $CHECKS checks passed.\"\n}\n",[506,8887,8888,8893,8898,8903,8914,8918,8948,8963,8973,8983,8987,9020,9030,9121,9135,9139,9164,9168,9175,9192,9200,9204,9208,9213,9220,9293,9297,9301,9306,9311,9317,9352,9380,9400,9420,9486,9491,9535,9540,9572,9605,9619,9694,9701,9705,9709,9714,9719,9725,9740,9745,9790,9804,9825,9859,9907,9937,9945,9950,9969,9982,9986,9990,9995,10001,10019,10049,10063,10067,10072,10080,10088,10094,10134,10179,10184,10189,10200,10205,10213,10228],{"__ignoreMap":944},[948,8889,8890],{"class":950,"line":951},[948,8891,8892],{"class":954},"# Shared by the Engine smoke scripts. Sends requests to a running Engine, prints each request and\n",[948,8894,8895],{"class":950,"line":958},[948,8896,8897],{"class":954},"# response as Markdown (the access token as $TOKEN), and stops the run at the first failed check.\n",[948,8899,8900],{"class":950,"line":986},[948,8901,8902],{"class":954},"# Needs curl and jq. Settings: ENGINE_URL, ENGINE_EMAIL, ENGINE_PASSWORD.\n",[948,8904,8905,8908,8911],{"class":950,"line":1002},[948,8906,8907],{"class":2876},"set",[948,8909,8910],{"class":1024}," -euo",[948,8912,8913],{"class":979}," pipefail\n",[948,8915,8916],{"class":950,"line":1009},[948,8917,1006],{"emptyLinePlaceholder":1005},[948,8919,8920,8922,8924,8927,8929,8932,8935,8938,8941,8943,8946],{"class":950,"line":1015},[948,8921,8767],{"class":969},[948,8923,1775],{"class":961},[948,8925,8926],{"class":965},"${",[948,8928,8767],{"class":969},[948,8930,8931],{"class":961},":-",[948,8933,8934],{"class":969},"http",[948,8936,8937],{"class":961},":\u002F\u002F",[948,8939,8940],{"class":969},"localhost",[948,8942,2322],{"class":961},[948,8944,8945],{"class":969},"8100",[948,8947,1311],{"class":965},[948,8949,8950,8953,8955,8957,8959,8961],{"class":950,"line":1036},[948,8951,8952],{"class":969},"REDACT",[948,8954,1775],{"class":961},[948,8956,8926],{"class":965},[948,8958,8952],{"class":969},[948,8960,8931],{"class":961},[948,8962,1311],{"class":965},[948,8964,8965,8968,8970],{"class":950,"line":1053},[948,8966,8967],{"class":969},"CHECKS",[948,8969,1775],{"class":961},[948,8971,8972],{"class":979},"0\n",[948,8974,8975,8978,8980],{"class":950,"line":1070},[948,8976,8977],{"class":969},"WORKSPACES",[948,8979,1775],{"class":961},[948,8981,8982],{"class":965},"()\n",[948,8984,8985],{"class":950,"line":1075},[948,8986,1006],{"emptyLinePlaceholder":1005},[948,8988,8989,8992,8994,8997,8999,9002,9005,9008,9011,9014,9017],{"class":950,"line":1081},[948,8990,8991],{"class":969},"TOKEN",[948,8993,1775],{"class":961},[948,8995,8996],{"class":965},"$(",[948,8998,8739],{"class":1024},[948,9000,9001],{"class":1024}," -sf",[948,9003,9004],{"class":1024}," -X",[948,9006,9007],{"class":979}," POST",[948,9009,9010],{"class":979}," \"",[948,9012,9013],{"class":969},"$ENGINE_URL",[948,9015,9016],{"class":979},"\u002Fapi\u002Fauth\u002Fproviders\u002Flocal\u002Fpassword\u002Flogin\"",[948,9018,9019],{"class":1024}," \\\n",[948,9021,9022,9025,9028],{"class":950,"line":1095},[948,9023,9024],{"class":1024},"    -H",[948,9026,9027],{"class":979}," 'Content-Type: application\u002Fjson'",[948,9029,9019],{"class":1024},[948,9031,9032,9035,9038,9041,9043,9045,9047,9049,9051,9053,9055,9057,9060,9063,9065,9068,9070,9072,9075,9077,9080,9082,9084,9086,9088,9090,9092,9094,9096,9098,9100,9102,9105,9107,9109,9111,9113,9115,9118],{"class":950,"line":1120},[948,9033,9034],{"class":1024},"    -d",[948,9036,9037],{"class":979}," \"{",[948,9039,9040],{"class":1024},"\\\"",[948,9042,1796],{"class":979},[948,9044,9040],{"class":1024},[948,9046,2322],{"class":979},[948,9048,9040],{"class":1024},[948,9050,8926],{"class":979},[948,9052,8782],{"class":969},[948,9054,8931],{"class":961},[948,9056,6779],{"class":969},[948,9058,9059],{"class":961},"@",[948,9061,9062],{"class":969},"example",[948,9064,476],{"class":979},[948,9066,9067],{"class":969},"com",[948,9069,2440],{"class":979},[948,9071,9040],{"class":1024},[948,9073,9074],{"class":979},",",[948,9076,9040],{"class":1024},[948,9078,9079],{"class":979},"password",[948,9081,9040],{"class":1024},[948,9083,2322],{"class":979},[948,9085,9040],{"class":1024},[948,9087,8926],{"class":979},[948,9089,8785],{"class":969},[948,9091,8931],{"class":961},[948,9093,6779],{"class":969},[948,9095,2440],{"class":979},[948,9097,9040],{"class":1024},[948,9099,9074],{"class":979},[948,9101,9040],{"class":1024},[948,9103,9104],{"class":979},"mode",[948,9106,9040],{"class":1024},[948,9108,2322],{"class":979},[948,9110,9040],{"class":1024},[948,9112,3336],{"class":979},[948,9114,9040],{"class":1024},[948,9116,9117],{"class":979},"}\"",[948,9119,9120],{"class":961}," |\n",[948,9122,9123,9126,9129,9132],{"class":950,"line":1195},[948,9124,9125],{"class":1024},"    jq",[948,9127,9128],{"class":1024}," -r",[948,9130,9131],{"class":979}," .accessToken",[948,9133,9134],{"class":965},")\n",[948,9136,9137],{"class":950,"line":1240},[948,9138,1006],{"emptyLinePlaceholder":1005},[948,9140,9141,9144,9147,9150,9153,9155,9158,9161],{"class":950,"line":1246},[948,9142,9143],{"class":1024},"say",[948,9145,9146],{"class":965},"() { ",[948,9148,9149],{"class":2876},"printf",[948,9151,9152],{"class":979}," '\\n%s\\n'",[948,9154,9010],{"class":979},[948,9156,9157],{"class":1024},"$*",[948,9159,9160],{"class":979},"\"",[948,9162,9163],{"class":965},"; }\n",[948,9165,9166],{"class":950,"line":1298},[948,9167,1006],{"emptyLinePlaceholder":1005},[948,9169,9170,9173],{"class":950,"line":1308},[948,9171,9172],{"class":1024},"fail",[948,9174,1092],{"class":965},[948,9176,9177,9180,9183,9185,9187,9189],{"class":950,"line":1591},[948,9178,9179],{"class":2876},"    printf",[948,9181,9182],{"class":979}," '\\nFAIL: %s\\n'",[948,9184,9010],{"class":979},[948,9186,9157],{"class":1024},[948,9188,9160],{"class":979},[948,9190,9191],{"class":961}," >&2\n",[948,9193,9194,9197],{"class":950,"line":1610},[948,9195,9196],{"class":2876},"    exit",[948,9198,9199],{"class":1024}," 1\n",[948,9201,9202],{"class":950,"line":1625},[948,9203,1311],{"class":965},[948,9205,9206],{"class":950,"line":1630},[948,9207,1006],{"emptyLinePlaceholder":1005},[948,9209,9210],{"class":950,"line":1636},[948,9211,9212],{"class":954},"# Replaces the secret in REDACT, when set, so it never reaches the output.\n",[948,9214,9215,9218],{"class":950,"line":1656},[948,9216,9217],{"class":1024},"redact",[948,9219,1092],{"class":965},[948,9221,9222,9224,9227,9230,9232,9235,9237,9240,9243,9246,9249,9251,9254,9257,9259,9261,9263,9265,9268,9270,9272,9274,9277,9279,9281,9283,9286,9288,9290],{"class":950,"line":1671},[948,9223,1198],{"class":961},[948,9225,9226],{"class":965}," [ ",[948,9228,9229],{"class":961},"-n",[948,9231,9010],{"class":979},[948,9233,9234],{"class":969},"$REDACT",[948,9236,9160],{"class":979},[948,9238,9239],{"class":965}," ]; ",[948,9241,9242],{"class":961},"then",[948,9244,9245],{"class":2876}," printf",[948,9247,9248],{"class":979}," '%s'",[948,9250,9010],{"class":979},[948,9252,9253],{"class":2876},"${1",[948,9255,9256],{"class":961},"\u002F\u002F",[948,9258,9160],{"class":979},[948,9260,9234],{"class":969},[948,9262,9160],{"class":979},[948,9264,1256],{"class":961},[948,9266,9267],{"class":969},"sk_test_YOUR_KEY",[948,9269,2440],{"class":2876},[948,9271,9160],{"class":979},[948,9273,3391],{"class":965},[948,9275,9276],{"class":961},"else",[948,9278,9245],{"class":2876},[948,9280,9248],{"class":979},[948,9282,9010],{"class":979},[948,9284,9285],{"class":2876},"$1",[948,9287,9160],{"class":979},[948,9289,3391],{"class":965},[948,9291,9292],{"class":961},"fi\n",[948,9294,9295],{"class":950,"line":1676},[948,9296,1311],{"class":965},[948,9298,9299],{"class":950,"line":1681},[948,9300,1006],{"emptyLinePlaceholder":1005},[948,9302,9303],{"class":950,"line":1686},[948,9304,9305],{"class":954},"# req METHOD PATH [BODY] [JQ_FILTER]: sends one request, prints it and the (filtered) response.\n",[948,9307,9308],{"class":950,"line":1699},[948,9309,9310],{"class":954},"# `Cache-Control: no-cache` makes Engine run every read, so no check passes on a cached result.\n",[948,9312,9313,9315],{"class":950,"line":1704},[948,9314,8866],{"class":1024},[948,9316,1092],{"class":965},[948,9318,9319,9322,9325,9328,9331,9334,9337,9340,9342,9345,9347,9349],{"class":950,"line":1736},[948,9320,9321],{"class":961},"    local",[948,9323,9324],{"class":969}," method",[948,9326,9327],{"class":961},"=$1",[948,9329,9330],{"class":969}," path",[948,9332,9333],{"class":961},"=$2",[948,9335,9336],{"class":969}," body",[948,9338,9339],{"class":961},"=${3:-}",[948,9341,4305],{"class":969},[948,9343,9344],{"class":961},"=${4:-",[948,9346,476],{"class":965},[948,9348,2440],{"class":961},[948,9350,9351],{"class":969}," out\n",[948,9353,9354,9356,9359,9361,9364,9366,9368,9370,9372,9374,9377],{"class":950,"line":1761},[948,9355,9179],{"class":2876},[948,9357,9358],{"class":979}," '\\n```bash\\ncurl -g -X %s \"%s%s\" \\\\\\n  -H \"Authorization: Bearer $TOKEN\" \\\\\\n  -H \"Cache-Control: no-cache\"'",[948,9360,9010],{"class":979},[948,9362,9363],{"class":969},"$method",[948,9365,9160],{"class":979},[948,9367,9010],{"class":979},[948,9369,9013],{"class":969},[948,9371,9160],{"class":979},[948,9373,9010],{"class":979},[948,9375,9376],{"class":969},"$path",[948,9378,9379],{"class":979},"\"\n",[948,9381,9382,9384,9386,9388,9390,9393,9395,9397],{"class":950,"line":1808},[948,9383,1198],{"class":961},[948,9385,9226],{"class":965},[948,9387,9229],{"class":961},[948,9389,9010],{"class":979},[948,9391,9392],{"class":969},"$body",[948,9394,9160],{"class":979},[948,9396,9239],{"class":965},[948,9398,9399],{"class":961},"then\n",[948,9401,9402,9405,9408,9411,9413,9415,9417],{"class":950,"line":1829},[948,9403,9404],{"class":2876},"        printf",[948,9406,9407],{"class":979}," ' \\\\\\n  -H \"Content-Type: application\u002Fjson\" \\\\\\n  -d '\"'\"'%s'\"'\"''",[948,9409,9410],{"class":979}," \"$(",[948,9412,9217],{"class":1024},[948,9414,9010],{"class":979},[948,9416,9392],{"class":969},[948,9418,9419],{"class":979},"\")\"\n",[948,9421,9422,9425,9427,9429,9431,9434,9436,9438,9440,9442,9444,9447,9449,9452,9455,9458,9460,9462,9465,9467,9469,9472,9474,9476,9478,9481,9484],{"class":950,"line":1834},[948,9423,9424],{"class":969},"        out",[948,9426,1775],{"class":961},[948,9428,8996],{"class":965},[948,9430,8739],{"class":1024},[948,9432,9433],{"class":1024}," -sg",[948,9435,9004],{"class":1024},[948,9437,9010],{"class":979},[948,9439,9363],{"class":969},[948,9441,9160],{"class":979},[948,9443,9010],{"class":979},[948,9445,9446],{"class":969},"$ENGINE_URL$path",[948,9448,9160],{"class":979},[948,9450,9451],{"class":1024}," -H",[948,9453,9454],{"class":979}," \"Authorization: Bearer ",[948,9456,9457],{"class":969},"$TOKEN",[948,9459,9160],{"class":979},[948,9461,9451],{"class":1024},[948,9463,9464],{"class":979}," 'Cache-Control: no-cache'",[948,9466,9451],{"class":1024},[948,9468,9027],{"class":979},[948,9470,9471],{"class":1024}," -d",[948,9473,9010],{"class":979},[948,9475,9392],{"class":969},[948,9477,9160],{"class":979},[948,9479,9480],{"class":1024}," -w",[948,9482,9483],{"class":979}," '\\n%{http_code}'",[948,9485,9134],{"class":965},[948,9487,9488],{"class":950,"line":1882},[948,9489,9490],{"class":961},"    else\n",[948,9492,9493,9495,9497,9499,9501,9503,9505,9507,9509,9511,9513,9515,9517,9519,9521,9523,9525,9527,9529,9531,9533],{"class":950,"line":1899},[948,9494,9424],{"class":969},[948,9496,1775],{"class":961},[948,9498,8996],{"class":965},[948,9500,8739],{"class":1024},[948,9502,9433],{"class":1024},[948,9504,9004],{"class":1024},[948,9506,9010],{"class":979},[948,9508,9363],{"class":969},[948,9510,9160],{"class":979},[948,9512,9010],{"class":979},[948,9514,9446],{"class":969},[948,9516,9160],{"class":979},[948,9518,9451],{"class":1024},[948,9520,9454],{"class":979},[948,9522,9457],{"class":969},[948,9524,9160],{"class":979},[948,9526,9451],{"class":1024},[948,9528,9464],{"class":979},[948,9530,9480],{"class":1024},[948,9532,9483],{"class":979},[948,9534,9134],{"class":965},[948,9536,9537],{"class":950,"line":1904},[948,9538,9539],{"class":961},"    fi\n",[948,9541,9542,9545,9547,9549,9551,9553,9555,9558,9560,9562,9565,9568,9570],{"class":950,"line":1909},[948,9543,9544],{"class":969},"    LAST_CODE",[948,9546,1775],{"class":961},[948,9548,8996],{"class":965},[948,9550,9149],{"class":2876},[948,9552,9248],{"class":979},[948,9554,9010],{"class":979},[948,9556,9557],{"class":969},"$out",[948,9559,9160],{"class":979},[948,9561,3281],{"class":961},[948,9563,9564],{"class":1024}," tail",[948,9566,9567],{"class":1024}," -n",[948,9569,7698],{"class":1024},[948,9571,9134],{"class":965},[948,9573,9574,9577,9579,9581,9583,9585,9587,9590,9592,9595,9597,9600,9603],{"class":950,"line":1942},[948,9575,9576],{"class":969},"    LAST_BODY",[948,9578,1775],{"class":961},[948,9580,8996],{"class":965},[948,9582,9217],{"class":1024},[948,9584,9410],{"class":979},[948,9586,9149],{"class":2876},[948,9588,9589],{"class":979}," '%s' \"",[948,9591,9557],{"class":969},[948,9593,9594],{"class":979},"\" ",[948,9596,1265],{"class":961},[948,9598,9599],{"class":1024}," sed",[948,9601,9602],{"class":979}," '$d')\"",[948,9604,9134],{"class":965},[948,9606,9607,9609,9612,9614,9617],{"class":950,"line":1989},[948,9608,9179],{"class":2876},[948,9610,9611],{"class":979}," '\\n```\\n\\n```json\\n\u002F\u002F HTTP %s\\n'",[948,9613,9010],{"class":979},[948,9615,9616],{"class":969},"$LAST_CODE",[948,9618,9379],{"class":979},[948,9620,9621,9623,9625,9628,9630,9633,9635,9637,9639,9642,9645,9647,9649,9651,9653,9655,9657,9659,9661,9664,9666,9669,9671,9674,9677,9679,9681,9684,9686,9688,9690,9692],{"class":950,"line":2018},[948,9622,1198],{"class":961},[948,9624,9226],{"class":965},[948,9626,9627],{"class":961},"-z",[948,9629,9010],{"class":979},[948,9631,9632],{"class":969},"$LAST_BODY",[948,9634,9160],{"class":979},[948,9636,9239],{"class":965},[948,9638,9242],{"class":961},[948,9640,9641],{"class":2876}," echo",[948,9643,9644],{"class":979}," '\u002F\u002F (empty body)'",[948,9646,3391],{"class":965},[948,9648,9276],{"class":961},[948,9650,9245],{"class":2876},[948,9652,9248],{"class":979},[948,9654,9010],{"class":979},[948,9656,9632],{"class":969},[948,9658,9160],{"class":979},[948,9660,3281],{"class":961},[948,9662,9663],{"class":1024}," jq",[948,9665,9010],{"class":979},[948,9667,9668],{"class":969},"$filter",[948,9670,9160],{"class":979},[948,9672,9673],{"class":961}," 2>",[948,9675,9676],{"class":979},"\u002Fdev\u002Fnull",[948,9678,1210],{"class":961},[948,9680,9245],{"class":2876},[948,9682,9683],{"class":979}," '%s\\n'",[948,9685,9010],{"class":979},[948,9687,9632],{"class":969},[948,9689,9160],{"class":979},[948,9691,3391],{"class":965},[948,9693,9292],{"class":961},[948,9695,9696,9698],{"class":950,"line":2029},[948,9697,9179],{"class":2876},[948,9699,9700],{"class":979}," '```\\n'\n",[948,9702,9703],{"class":950,"line":2035},[948,9704,1311],{"class":965},[948,9706,9707],{"class":950,"line":2040},[948,9708,1006],{"emptyLinePlaceholder":1005},[948,9710,9711],{"class":950,"line":2055},[948,9712,9713],{"class":954},"# expect STATUS [JQ_EXPRESSION EXPECTED_JSON]...: checks the last response's status, then each\n",[948,9715,9716],{"class":950,"line":2100},[948,9717,9718],{"class":954},"# expression's compact JSON result against the expected value.\n",[948,9720,9721,9723],{"class":950,"line":2116},[948,9722,3133],{"class":1024},[948,9724,1092],{"class":965},[948,9726,9727,9729,9732,9734,9737],{"class":950,"line":2122},[948,9728,9321],{"class":961},[948,9730,9731],{"class":969}," status",[948,9733,9327],{"class":961},[948,9735,9736],{"class":969}," checked",[948,9738,9739],{"class":969}," actual\n",[948,9741,9742],{"class":950,"line":2127},[948,9743,9744],{"class":2876},"    shift\n",[948,9746,9747,9750,9752,9754,9756,9758,9760,9763,9765,9768,9771,9774,9777,9779,9782,9784,9786,9788],{"class":950,"line":2161},[948,9748,9749],{"class":965},"    [ ",[948,9751,9160],{"class":979},[948,9753,9616],{"class":969},[948,9755,9160],{"class":979},[948,9757,1028],{"class":961},[948,9759,9010],{"class":979},[948,9761,9762],{"class":969},"$status",[948,9764,9160],{"class":979},[948,9766,9767],{"class":965}," ] ",[948,9769,9770],{"class":961},"||",[948,9772,9773],{"class":1024}," fail",[948,9775,9776],{"class":979}," \"expected HTTP ",[948,9778,9762],{"class":969},[948,9780,9781],{"class":979},", got ",[948,9783,9616],{"class":969},[948,9785,1866],{"class":979},[948,9787,9632],{"class":969},[948,9789,9379],{"class":979},[948,9791,9792,9795,9797,9800,9802],{"class":950,"line":2166},[948,9793,9794],{"class":969},"    checked",[948,9796,1775],{"class":961},[948,9798,9799],{"class":979},"\"HTTP ",[948,9801,9762],{"class":969},[948,9803,9379],{"class":979},[948,9805,9806,9809,9811,9814,9817,9820,9822],{"class":950,"line":2200},[948,9807,9808],{"class":961},"    while",[948,9810,9226],{"class":965},[948,9812,9813],{"class":1024},"$#",[948,9815,9816],{"class":961}," -ge",[948,9818,9819],{"class":1024}," 2",[948,9821,9239],{"class":965},[948,9823,9824],{"class":961},"do\n",[948,9826,9827,9830,9832,9834,9836,9838,9840,9842,9844,9846,9848,9851,9853,9855,9857],{"class":950,"line":2242},[948,9828,9829],{"class":969},"        actual",[948,9831,1775],{"class":961},[948,9833,8996],{"class":965},[948,9835,9149],{"class":2876},[948,9837,9248],{"class":979},[948,9839,9010],{"class":979},[948,9841,9632],{"class":969},[948,9843,9160],{"class":979},[948,9845,3281],{"class":961},[948,9847,9663],{"class":1024},[948,9849,9850],{"class":1024}," -c",[948,9852,9010],{"class":979},[948,9854,9285],{"class":2876},[948,9856,9160],{"class":979},[948,9858,9134],{"class":965},[948,9860,9861,9864,9866,9869,9871,9873,9875,9878,9880,9882,9884,9886,9889,9892,9894,9896,9899,9901,9903,9905],{"class":950,"line":2247},[948,9862,9863],{"class":965},"        [ ",[948,9865,9160],{"class":979},[948,9867,9868],{"class":969},"$actual",[948,9870,9160],{"class":979},[948,9872,1028],{"class":961},[948,9874,9010],{"class":979},[948,9876,9877],{"class":2876},"$2",[948,9879,9160],{"class":979},[948,9881,9767],{"class":965},[948,9883,9770],{"class":961},[948,9885,9773],{"class":1024},[948,9887,9888],{"class":979}," \"expected ",[948,9890,9891],{"class":1024},"\\`",[948,9893,9285],{"class":2876},[948,9895,9891],{"class":1024},[948,9897,9898],{"class":979}," to be ",[948,9900,9877],{"class":2876},[948,9902,9781],{"class":979},[948,9904,9868],{"class":969},[948,9906,9379],{"class":979},[948,9908,9909,9912,9914,9916,9919,9921,9923,9925,9927,9929,9931,9933,9935],{"class":950,"line":2265},[948,9910,9911],{"class":969},"        checked",[948,9913,1775],{"class":961},[948,9915,9160],{"class":979},[948,9917,9918],{"class":969},"$checked",[948,9920,3391],{"class":979},[948,9922,9891],{"class":1024},[948,9924,9285],{"class":2876},[948,9926,9891],{"class":1024},[948,9928,5270],{"class":979},[948,9930,9891],{"class":1024},[948,9932,9877],{"class":2876},[948,9934,9891],{"class":1024},[948,9936,9379],{"class":979},[948,9938,9939,9942],{"class":950,"line":2276},[948,9940,9941],{"class":2876},"        shift",[948,9943,9944],{"class":1024}," 2\n",[948,9946,9947],{"class":950,"line":2287},[948,9948,9949],{"class":961},"    done\n",[948,9951,9952,9955,9957,9960,9962,9964,9966],{"class":950,"line":2295},[948,9953,9954],{"class":969},"    CHECKS",[948,9956,1775],{"class":961},[948,9958,9959],{"class":965},"$((",[948,9961,8967],{"class":1024},[948,9963,7695],{"class":979},[948,9965,7698],{"class":1024},[948,9967,9968],{"class":965},"))\n",[948,9970,9971,9973,9976,9978,9980],{"class":950,"line":2303},[948,9972,9179],{"class":2876},[948,9974,9975],{"class":979}," '\\nChecked: %s\\n'",[948,9977,9010],{"class":979},[948,9979,9918],{"class":969},[948,9981,9379],{"class":979},[948,9983,9984],{"class":950,"line":2337},[948,9985,1311],{"class":965},[948,9987,9988],{"class":950,"line":2349},[948,9989,1006],{"emptyLinePlaceholder":1005},[948,9991,9992],{"class":950,"line":2361},[948,9993,9994],{"class":954},"# workspace NAME: creates a throwaway workspace that is deleted when the script exits, even on failure.\n",[948,9996,9997,9999],{"class":950,"line":2367},[948,9998,470],{"class":1024},[948,10000,1092],{"class":965},[948,10002,10003,10006,10009,10011,10013,10015,10017],{"class":950,"line":2382},[948,10004,10005],{"class":969},"    WORKSPACES",[948,10007,10008],{"class":961},"+=",[948,10010,1176],{"class":965},[948,10012,9160],{"class":979},[948,10014,9285],{"class":2876},[948,10016,9160],{"class":979},[948,10018,9134],{"class":965},[948,10020,10021,10024,10026,10029,10031,10033,10036,10038,10040,10042,10044,10046],{"class":950,"line":2387},[948,10022,10023],{"class":1024},"    req",[948,10025,9007],{"class":979},[948,10027,10028],{"class":979}," \u002Fapi\u002Fsystem\u002Fworkspaces",[948,10030,9037],{"class":979},[948,10032,9040],{"class":1024},[948,10034,10035],{"class":979},"apiName",[948,10037,9040],{"class":1024},[948,10039,2322],{"class":979},[948,10041,9040],{"class":1024},[948,10043,9285],{"class":2876},[948,10045,9040],{"class":1024},[948,10047,10048],{"class":979},"}\"\n",[948,10050,10051,10054,10057,10060],{"class":950,"line":2392},[948,10052,10053],{"class":1024},"    expect",[948,10055,10056],{"class":1024}," 200",[948,10058,10059],{"class":979}," '.data.id | type'",[948,10061,10062],{"class":979}," '\"string\"'\n",[948,10064,10065],{"class":950,"line":2397},[948,10066,1311],{"class":965},[948,10068,10070],{"class":950,"line":10069},69,[948,10071,1006],{"emptyLinePlaceholder":1005},[948,10073,10075,10078],{"class":950,"line":10074},70,[948,10076,10077],{"class":1024},"cleanup",[948,10079,1092],{"class":965},[948,10081,10083,10085],{"class":950,"line":10082},71,[948,10084,9321],{"class":961},[948,10086,10087],{"class":969}," name\n",[948,10089,10091],{"class":950,"line":10090},72,[948,10092,10093],{"class":954},"    # The `+` form keeps an empty array from failing `set -u` in older bash.\n",[948,10095,10097,10099,10102,10105,10108,10110,10112,10114,10117,10120,10122,10124,10126,10129,10132],{"class":950,"line":10096},73,[948,10098,2169],{"class":961},[948,10100,10101],{"class":969}," name",[948,10103,10104],{"class":961}," in",[948,10106,10107],{"class":965}," ${",[948,10109,8977],{"class":969},[948,10111,5004],{"class":965},[948,10113,9059],{"class":961},[948,10115,10116],{"class":965},"]+",[948,10118,10119],{"class":979},"\"${",[948,10121,8977],{"class":969},[948,10123,5004],{"class":979},[948,10125,9059],{"class":961},[948,10127,10128],{"class":979},"]}\"",[948,10130,10131],{"class":965},"}; ",[948,10133,9824],{"class":961},[948,10135,10137,10140,10142,10145,10148,10150,10153,10155,10157,10160,10163,10166,10168,10170,10172,10174,10176],{"class":950,"line":10136},74,[948,10138,10139],{"class":1024},"        curl",[948,10141,9433],{"class":1024},[948,10143,10144],{"class":1024}," -o",[948,10146,10147],{"class":979}," \u002Fdev\u002Fnull",[948,10149,9004],{"class":1024},[948,10151,10152],{"class":979}," DELETE",[948,10154,9010],{"class":979},[948,10156,9013],{"class":969},[948,10158,10159],{"class":979},"\u002Fapi\u002Fsystem\u002Fworkspaces\u002F",[948,10161,10162],{"class":969},"$name",[948,10164,10165],{"class":979},"?fields=id\"",[948,10167,9451],{"class":1024},[948,10169,9454],{"class":979},[948,10171,9457],{"class":969},[948,10173,9160],{"class":979},[948,10175,1210],{"class":961},[948,10177,10178],{"class":2876}," true\n",[948,10180,10182],{"class":950,"line":10181},75,[948,10183,9949],{"class":961},[948,10185,10187],{"class":950,"line":10186},76,[948,10188,1311],{"class":965},[948,10190,10192,10194,10197],{"class":950,"line":10191},77,[948,10193,8876],{"class":2876},[948,10195,10196],{"class":979}," cleanup",[948,10198,10199],{"class":979}," EXIT\n",[948,10201,10203],{"class":950,"line":10202},78,[948,10204,1006],{"emptyLinePlaceholder":1005},[948,10206,10208,10211],{"class":950,"line":10207},79,[948,10209,10210],{"class":1024},"finish",[948,10212,1092],{"class":965},[948,10214,10216,10219,10222,10225],{"class":950,"line":10215},80,[948,10217,10218],{"class":1024},"    say",[948,10220,10221],{"class":979}," \"All ",[948,10223,10224],{"class":969},"$CHECKS",[948,10226,10227],{"class":979}," checks passed.\"\n",[948,10229,10231],{"class":950,"line":10230},81,[948,10232,1311],{"class":965},[458,10234,10235],{},"The Stripe script starts by refusing any key that isn't test mode, then walks through the steps below:",[1333,10237,10238],{},[1336,10239,10241],{"label":10240},"scripts\u002Fengine-smoke.sh (setup, handshake, credentials)",[938,10242,10244],{"className":2501,"code":10243,"filename":777,"language":2504,"meta":944,"style":944},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# Engine smoke for the Stripe connector. Needs a running Engine with the bundle installed, the\n# fixture from `npm run seed`, and a test-mode key in STRIPE_API_KEY (or a file in\n# STRIPE_API_KEY_FILE). Prints Markdown with the key redacted, and exits non-zero on the first\n# failed check. Leaves one archived price on `prod_msfx_locked` per run: Stripe can't delete prices.\nKEY=${STRIPE_API_KEY:-$(cat \"${STRIPE_API_KEY_FILE:?set STRIPE_API_KEY or STRIPE_API_KEY_FILE}\")}\n# The script writes to the account: never point it at live data. Checked before any request.\n[[ $KEY =~ ^(sk|rk)_test_ ]] || { echo 'Use a Stripe test-mode key.' >&2; exit 1; }\nREDACT=$KEY\n. \"$(dirname \"$0\")\u002Fengine-lib.sh\"\n\nSTAMP=$(date +%s)\nWS=stripe-$STAMP\nREAD_ONLY_WS=stripe-ro-$STAMP\nPROVIDER=example\u002Fstripe-connector\nPERMISSIONS='[{\"permission\":\"net:api.stripe.com\",\"reason\":\"Read and write customers, products and prices through the Stripe API\"}]'\nFIXTURE=stripe-fixture.monospace.test\n\nCONFIG=\"{\\\"apiKey\\\":\\\"$KEY\\\"}\"\n# Read-only, and with a budget of two Stripe objects per read, to see a QueryRejected through Engine.\nREAD_ONLY_CONFIG=\"{\\\"apiKey\\\":\\\"$KEY\\\",\\\"enableWrites\\\":false,\\\"maxItemsPerQuery\\\":2}\"\n\n# source_body CONFIGURATION [API_NAME]: a data source for this connector, with the approved permissions.\nsource_body() {\n    if [ -n \"${2:-}\" ]; then\n        printf '{\"apiName\":\"%s\",\"provider\":\"%s\",\"config\":%s,\"permissions\":%s}' \"$2\" \"$PROVIDER\" \"$1\" \"$PERMISSIONS\"\n    else\n        printf '{\"provider\":\"%s\",\"config\":%s,\"permissions\":%s}' \"$PROVIDER\" \"$1\" \"$PERMISSIONS\"\n    fi\n}\n# Stripe's own answer, to compare Engine's answer with.\nstripe() { curl -sfg \"https:\u002F\u002Fapi.stripe.com\u002Fv1\u002F$1\" -u \"$KEY:\" -H 'Stripe-Version: 2026-08-26.dahlia'; }\n\nsay \"### Create a throwaway workspace\"\nworkspace \"$WS\"\n\nsay \"### Handshake: without permissions, Engine answers 7001 with the set to approve\"\nreq POST \"\u002Fapi\u002F$WS\u002Fsources\u002Fdata\u002Ftest\" \"{\\\"provider\\\":\\\"$PROVIDER\\\",\\\"config\\\":$CONFIG}\"\nexpect 422 '.code' '\"7001\"' '.meta.permissions' \"$PERMISSIONS\"\n\nsay \"### A well-formed wrong key: setup accepts it, testConnection gets Stripe's 401 (AccessDenied)\"\nreq POST \"\u002Fapi\u002F$WS\u002Fsources\u002Fdata\u002Ftest\" \"$(source_body '{\"apiKey\":\"sk_test_wrongwrongwrong\"}')\"\nexpect 422 '.source.message' '\"Invalid extension configuration\"' \\\n    'tostring | contains(\"Stripe rejected the API key (HTTP 401).\")' 'true' 'tostring | contains(\"wrong\")' 'false'\n\nsay \"### A malformed key: setup refuses it with InvalidConfiguration, which fails the test with 422\"\nreq POST \"\u002Fapi\u002F$WS\u002Fsources\u002Fdata\u002Ftest\" \"$(source_body '{\"apiKey\":\"not-a-key\"}')\"\nexpect 422 '.message' '\"The extension could not be started\"' \\\n    '.source.source.message' '\"Invalid extension configuration\"' 'tostring | contains(\"must be a secret\")' 'true'\n\nsay \"### Test with the real test key\"\nreq POST \"\u002Fapi\u002F$WS\u002Fsources\u002Fdata\u002Ftest\" \"$(source_body \"$CONFIG\")\"\nexpect 204\n",[506,10245,10246,10251,10256,10261,10266,10271,10313,10318,10365,10374,10390,10394,10411,10424,10436,10446,10456,10466,10470,10496,10501,10545,10549,10554,10561,10584,10617,10621,10646,10650,10654,10659,10695,10699,10706,10717,10721,10728,10774,10796,10800,10807,10826,10840,10854,10858,10865,10884,10898,10911,10915,10922,10944],{"__ignoreMap":944},[948,10247,10248],{"class":950,"line":951},[948,10249,10250],{"class":954},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[948,10252,10253],{"class":950,"line":958},[948,10254,10255],{"class":954},"# Engine smoke for the Stripe connector. Needs a running Engine with the bundle installed, the\n",[948,10257,10258],{"class":950,"line":986},[948,10259,10260],{"class":954},"# fixture from `npm run seed`, and a test-mode key in STRIPE_API_KEY (or a file in\n",[948,10262,10263],{"class":950,"line":1002},[948,10264,10265],{"class":954},"# STRIPE_API_KEY_FILE). Prints Markdown with the key redacted, and exits non-zero on the first\n",[948,10267,10268],{"class":950,"line":1009},[948,10269,10270],{"class":954},"# failed check. Leaves one archived price on `prod_msfx_locked` per run: Stripe can't delete prices.\n",[948,10272,10273,10276,10278,10280,10282,10284,10286,10289,10292,10294,10297,10299,10302,10305,10308,10310],{"class":950,"line":1015},[948,10274,10275],{"class":969},"KEY",[948,10277,1775],{"class":961},[948,10279,8926],{"class":965},[948,10281,1138],{"class":969},[948,10283,8931],{"class":961},[948,10285,8996],{"class":965},[948,10287,10288],{"class":969},"cat",[948,10290,10291],{"class":979}," \"${",[948,10293,1115],{"class":969},[948,10295,10296],{"class":961},":?",[948,10298,8907],{"class":969},[948,10300,10301],{"class":969}," STRIPE_API_KEY",[948,10303,10304],{"class":969}," or",[948,10306,10307],{"class":969}," STRIPE_API_KEY_FILE",[948,10309,9117],{"class":979},[948,10311,10312],{"class":965},")}\n",[948,10314,10315],{"class":950,"line":1036},[948,10316,10317],{"class":954},"# The script writes to the account: never point it at live data. Checked before any request.\n",[948,10319,10320,10323,10326,10329,10332,10335,10337,10340,10343,10345,10347,10350,10353,10356,10358,10361,10363],{"class":950,"line":1053},[948,10321,10322],{"class":965},"[[ ",[948,10324,10325],{"class":969},"$KEY",[948,10327,10328],{"class":961}," =~",[948,10330,10331],{"class":965}," ^(",[948,10333,10334],{"class":969},"sk",[948,10336,1265],{"class":961},[948,10338,10339],{"class":969},"rk",[948,10341,10342],{"class":965},")_test_ ]] ",[948,10344,9770],{"class":961},[948,10346,966],{"class":965},[948,10348,10349],{"class":2876},"echo",[948,10351,10352],{"class":979}," 'Use a Stripe test-mode key.'",[948,10354,10355],{"class":961}," >&2",[948,10357,3391],{"class":965},[948,10359,10360],{"class":2876},"exit",[948,10362,7698],{"class":1024},[948,10364,9163],{"class":965},[948,10366,10367,10369,10371],{"class":950,"line":1070},[948,10368,8952],{"class":969},[948,10370,1775],{"class":961},[948,10372,10373],{"class":969},"$KEY\n",[948,10375,10376,10378,10380,10382,10384,10387],{"class":950,"line":1075},[948,10377,476],{"class":2876},[948,10379,9410],{"class":979},[948,10381,2882],{"class":1024},[948,10383,9010],{"class":979},[948,10385,10386],{"class":2876},"$0",[948,10388,10389],{"class":979},"\")\u002Fengine-lib.sh\"\n",[948,10391,10392],{"class":950,"line":1081},[948,10393,1006],{"emptyLinePlaceholder":1005},[948,10395,10396,10399,10401,10403,10406,10409],{"class":950,"line":1095},[948,10397,10398],{"class":969},"STAMP",[948,10400,1775],{"class":961},[948,10402,8996],{"class":965},[948,10404,10405],{"class":1024},"date",[948,10407,10408],{"class":979}," +%s",[948,10410,9134],{"class":965},[948,10412,10413,10416,10418,10421],{"class":950,"line":1120},[948,10414,10415],{"class":969},"WS",[948,10417,1775],{"class":961},[948,10419,10420],{"class":979},"stripe-",[948,10422,10423],{"class":969},"$STAMP\n",[948,10425,10426,10429,10431,10434],{"class":950,"line":1195},[948,10427,10428],{"class":969},"READ_ONLY_WS",[948,10430,1775],{"class":961},[948,10432,10433],{"class":979},"stripe-ro-",[948,10435,10423],{"class":969},[948,10437,10438,10441,10443],{"class":950,"line":1240},[948,10439,10440],{"class":969},"PROVIDER",[948,10442,1775],{"class":961},[948,10444,10445],{"class":979},"example\u002Fstripe-connector\n",[948,10447,10448,10451,10453],{"class":950,"line":1246},[948,10449,10450],{"class":969},"PERMISSIONS",[948,10452,1775],{"class":961},[948,10454,10455],{"class":979},"'[{\"permission\":\"net:api.stripe.com\",\"reason\":\"Read and write customers, products and prices through the Stripe API\"}]'\n",[948,10457,10458,10461,10463],{"class":950,"line":1298},[948,10459,10460],{"class":969},"FIXTURE",[948,10462,1775],{"class":961},[948,10464,10465],{"class":979},"stripe-fixture.monospace.test\n",[948,10467,10468],{"class":950,"line":1308},[948,10469,1006],{"emptyLinePlaceholder":1005},[948,10471,10472,10475,10477,10480,10482,10484,10486,10488,10490,10492,10494],{"class":950,"line":1591},[948,10473,10474],{"class":969},"CONFIG",[948,10476,1775],{"class":961},[948,10478,10479],{"class":979},"\"{",[948,10481,9040],{"class":1024},[948,10483,5546],{"class":979},[948,10485,9040],{"class":1024},[948,10487,2322],{"class":979},[948,10489,9040],{"class":1024},[948,10491,10325],{"class":969},[948,10493,9040],{"class":1024},[948,10495,10048],{"class":979},[948,10497,10498],{"class":950,"line":1610},[948,10499,10500],{"class":954},"# Read-only, and with a budget of two Stripe objects per read, to see a QueryRejected through Engine.\n",[948,10502,10503,10506,10508,10510,10512,10514,10516,10518,10520,10522,10524,10526,10528,10530,10532,10535,10537,10540,10542],{"class":950,"line":1625},[948,10504,10505],{"class":969},"READ_ONLY_CONFIG",[948,10507,1775],{"class":961},[948,10509,10479],{"class":979},[948,10511,9040],{"class":1024},[948,10513,5546],{"class":979},[948,10515,9040],{"class":1024},[948,10517,2322],{"class":979},[948,10519,9040],{"class":1024},[948,10521,10325],{"class":969},[948,10523,9040],{"class":1024},[948,10525,9074],{"class":979},[948,10527,9040],{"class":1024},[948,10529,6349],{"class":979},[948,10531,9040],{"class":1024},[948,10533,10534],{"class":979},":false,",[948,10536,9040],{"class":1024},[948,10538,10539],{"class":979},"maxItemsPerQuery",[948,10541,9040],{"class":1024},[948,10543,10544],{"class":979},":2}\"\n",[948,10546,10547],{"class":950,"line":1630},[948,10548,1006],{"emptyLinePlaceholder":1005},[948,10550,10551],{"class":950,"line":1636},[948,10552,10553],{"class":954},"# source_body CONFIGURATION [API_NAME]: a data source for this connector, with the approved permissions.\n",[948,10555,10556,10559],{"class":950,"line":1656},[948,10557,10558],{"class":1024},"source_body",[948,10560,1092],{"class":965},[948,10562,10563,10565,10567,10569,10571,10574,10576,10578,10580,10582],{"class":950,"line":1671},[948,10564,1198],{"class":961},[948,10566,9226],{"class":965},[948,10568,9229],{"class":961},[948,10570,9010],{"class":979},[948,10572,10573],{"class":2876},"${2",[948,10575,8931],{"class":961},[948,10577,2440],{"class":2876},[948,10579,9160],{"class":979},[948,10581,9239],{"class":965},[948,10583,9399],{"class":961},[948,10585,10586,10588,10591,10593,10595,10597,10599,10602,10604,10606,10608,10610,10612,10615],{"class":950,"line":1676},[948,10587,9404],{"class":2876},[948,10589,10590],{"class":979}," '{\"apiName\":\"%s\",\"provider\":\"%s\",\"config\":%s,\"permissions\":%s}'",[948,10592,9010],{"class":979},[948,10594,9877],{"class":2876},[948,10596,9160],{"class":979},[948,10598,9010],{"class":979},[948,10600,10601],{"class":969},"$PROVIDER",[948,10603,9160],{"class":979},[948,10605,9010],{"class":979},[948,10607,9285],{"class":2876},[948,10609,9160],{"class":979},[948,10611,9010],{"class":979},[948,10613,10614],{"class":969},"$PERMISSIONS",[948,10616,9379],{"class":979},[948,10618,10619],{"class":950,"line":1681},[948,10620,9490],{"class":961},[948,10622,10623,10625,10628,10630,10632,10634,10636,10638,10640,10642,10644],{"class":950,"line":1686},[948,10624,9404],{"class":2876},[948,10626,10627],{"class":979}," '{\"provider\":\"%s\",\"config\":%s,\"permissions\":%s}'",[948,10629,9010],{"class":979},[948,10631,10601],{"class":969},[948,10633,9160],{"class":979},[948,10635,9010],{"class":979},[948,10637,9285],{"class":2876},[948,10639,9160],{"class":979},[948,10641,9010],{"class":979},[948,10643,10614],{"class":969},[948,10645,9379],{"class":979},[948,10647,10648],{"class":950,"line":1699},[948,10649,9539],{"class":961},[948,10651,10652],{"class":950,"line":1704},[948,10653,1311],{"class":965},[948,10655,10656],{"class":950,"line":1736},[948,10657,10658],{"class":954},"# Stripe's own answer, to compare Engine's answer with.\n",[948,10660,10661,10664,10666,10668,10671,10674,10676,10678,10681,10683,10685,10688,10690,10693],{"class":950,"line":1761},[948,10662,10663],{"class":1024},"stripe",[948,10665,9146],{"class":965},[948,10667,8739],{"class":1024},[948,10669,10670],{"class":1024}," -sfg",[948,10672,10673],{"class":979}," \"https:\u002F\u002Fapi.stripe.com\u002Fv1\u002F",[948,10675,9285],{"class":2876},[948,10677,9160],{"class":979},[948,10679,10680],{"class":1024}," -u",[948,10682,9010],{"class":979},[948,10684,10325],{"class":969},[948,10686,10687],{"class":979},":\"",[948,10689,9451],{"class":1024},[948,10691,10692],{"class":979}," 'Stripe-Version: 2026-08-26.dahlia'",[948,10694,9163],{"class":965},[948,10696,10697],{"class":950,"line":1808},[948,10698,1006],{"emptyLinePlaceholder":1005},[948,10700,10701,10703],{"class":950,"line":1829},[948,10702,9143],{"class":1024},[948,10704,10705],{"class":979}," \"### Create a throwaway workspace\"\n",[948,10707,10708,10710,10712,10715],{"class":950,"line":1834},[948,10709,470],{"class":1024},[948,10711,9010],{"class":979},[948,10713,10714],{"class":969},"$WS",[948,10716,9379],{"class":979},[948,10718,10719],{"class":950,"line":1882},[948,10720,1006],{"emptyLinePlaceholder":1005},[948,10722,10723,10725],{"class":950,"line":1899},[948,10724,9143],{"class":1024},[948,10726,10727],{"class":979}," \"### Handshake: without permissions, Engine answers 7001 with the set to approve\"\n",[948,10729,10730,10732,10734,10737,10739,10742,10744,10746,10749,10751,10753,10755,10757,10759,10761,10763,10765,10767,10769,10772],{"class":950,"line":1904},[948,10731,8866],{"class":1024},[948,10733,9007],{"class":979},[948,10735,10736],{"class":979}," \"\u002Fapi\u002F",[948,10738,10714],{"class":969},[948,10740,10741],{"class":979},"\u002Fsources\u002Fdata\u002Ftest\"",[948,10743,9037],{"class":979},[948,10745,9040],{"class":1024},[948,10747,10748],{"class":979},"provider",[948,10750,9040],{"class":1024},[948,10752,2322],{"class":979},[948,10754,9040],{"class":1024},[948,10756,10601],{"class":969},[948,10758,9040],{"class":1024},[948,10760,9074],{"class":979},[948,10762,9040],{"class":1024},[948,10764,3626],{"class":979},[948,10766,9040],{"class":1024},[948,10768,2322],{"class":979},[948,10770,10771],{"class":969},"$CONFIG",[948,10773,10048],{"class":979},[948,10775,10776,10778,10781,10784,10787,10790,10792,10794],{"class":950,"line":1909},[948,10777,3133],{"class":1024},[948,10779,10780],{"class":1024}," 422",[948,10782,10783],{"class":979}," '.code'",[948,10785,10786],{"class":979}," '\"7001\"'",[948,10788,10789],{"class":979}," '.meta.permissions'",[948,10791,9010],{"class":979},[948,10793,10614],{"class":969},[948,10795,9379],{"class":979},[948,10797,10798],{"class":950,"line":1942},[948,10799,1006],{"emptyLinePlaceholder":1005},[948,10801,10802,10804],{"class":950,"line":1989},[948,10803,9143],{"class":1024},[948,10805,10806],{"class":979}," \"### A well-formed wrong key: setup accepts it, testConnection gets Stripe's 401 (AccessDenied)\"\n",[948,10808,10809,10811,10813,10815,10817,10819,10821,10823],{"class":950,"line":2018},[948,10810,8866],{"class":1024},[948,10812,9007],{"class":979},[948,10814,10736],{"class":979},[948,10816,10714],{"class":969},[948,10818,10741],{"class":979},[948,10820,9410],{"class":979},[948,10822,10558],{"class":1024},[948,10824,10825],{"class":979}," '{\"apiKey\":\"sk_test_wrongwrongwrong\"}')\"\n",[948,10827,10828,10830,10832,10835,10838],{"class":950,"line":2029},[948,10829,3133],{"class":1024},[948,10831,10780],{"class":1024},[948,10833,10834],{"class":979}," '.source.message'",[948,10836,10837],{"class":979}," '\"Invalid extension configuration\"'",[948,10839,9019],{"class":1024},[948,10841,10842,10845,10848,10851],{"class":950,"line":2035},[948,10843,10844],{"class":979},"    'tostring | contains(\"Stripe rejected the API key (HTTP 401).\")'",[948,10846,10847],{"class":979}," 'true'",[948,10849,10850],{"class":979}," 'tostring | contains(\"wrong\")'",[948,10852,10853],{"class":979}," 'false'\n",[948,10855,10856],{"class":950,"line":2040},[948,10857,1006],{"emptyLinePlaceholder":1005},[948,10859,10860,10862],{"class":950,"line":2055},[948,10861,9143],{"class":1024},[948,10863,10864],{"class":979}," \"### A malformed key: setup refuses it with InvalidConfiguration, which fails the test with 422\"\n",[948,10866,10867,10869,10871,10873,10875,10877,10879,10881],{"class":950,"line":2100},[948,10868,8866],{"class":1024},[948,10870,9007],{"class":979},[948,10872,10736],{"class":979},[948,10874,10714],{"class":969},[948,10876,10741],{"class":979},[948,10878,9410],{"class":979},[948,10880,10558],{"class":1024},[948,10882,10883],{"class":979}," '{\"apiKey\":\"not-a-key\"}')\"\n",[948,10885,10886,10888,10890,10893,10896],{"class":950,"line":2116},[948,10887,3133],{"class":1024},[948,10889,10780],{"class":1024},[948,10891,10892],{"class":979}," '.message'",[948,10894,10895],{"class":979}," '\"The extension could not be started\"'",[948,10897,9019],{"class":1024},[948,10899,10900,10903,10905,10908],{"class":950,"line":2122},[948,10901,10902],{"class":979},"    '.source.source.message'",[948,10904,10837],{"class":979},[948,10906,10907],{"class":979}," 'tostring | contains(\"must be a secret\")'",[948,10909,10910],{"class":979}," 'true'\n",[948,10912,10913],{"class":950,"line":2127},[948,10914,1006],{"emptyLinePlaceholder":1005},[948,10916,10917,10919],{"class":950,"line":2161},[948,10918,9143],{"class":1024},[948,10920,10921],{"class":979}," \"### Test with the real test key\"\n",[948,10923,10924,10926,10928,10930,10932,10934,10936,10938,10940,10942],{"class":950,"line":2166},[948,10925,8866],{"class":1024},[948,10927,9007],{"class":979},[948,10929,10736],{"class":979},[948,10931,10714],{"class":969},[948,10933,10741],{"class":979},[948,10935,9410],{"class":979},[948,10937,10558],{"class":1024},[948,10939,9010],{"class":979},[948,10941,10771],{"class":969},[948,10943,9419],{"class":979},[948,10945,10946,10948],{"class":950,"line":2200},[948,10947,3133],{"class":1024},[948,10949,10950],{"class":1024}," 204\n",[458,10952,10953,10955,10956,10958,10959,10961,10962,10965],{},[506,10954,8866],{},"'s optional fourth argument is a ",[506,10957,8742],{}," filter that trims the printed response. ",[506,10960,3133],{}," always checks the whole body, after the configured key is redacted from it. ",[506,10963,10964],{},"LAST_BODY"," holds the last response, so a script can read an id from it for the next request.",[458,10967,10968],{},"Work through these steps in order:",[10970,10971,10972,10976,11000,11092,11109,11113,11120,11128,11155,11172,11176,11186,11199,11209,11213,11228,11241,11245,11252,11328,11331,11537,11544,11548],"steps",{},[2450,10973,10975],{"id":10974},"check-the-permission-handshake","Check the Permission Handshake",[458,10977,8724,10978,10981,10982,10985,10986,10989,10990,10993,10994,10996,10997,2322],{},[506,10979,10980],{},"POST \u002Fapi\u002F{workspace}\u002Fsources\u002Fdata\u002Ftest"," without ",[506,10983,10984],{},"permissions",". When your connector needs any host, expect ",[506,10987,10988],{},"422"," with code ",[506,10991,10992],{},"7001"," and exactly the required set (manifest hosts plus ",[506,10995,6283],{}," hosts) in ",[506,10998,10999],{},"meta.permissions",[938,11001,11003],{"className":8560,"code":11002,"filename":8562,"language":3336,"meta":944,"style":944},"{\n  \"message\": \"The connector requires permissions this data source was not granted. Approve them and retry.\",\n  \"code\": \"7001\",\n  \"meta\": {\n    \"permissions\": [\n      {\n        \"permission\": \"net:api.stripe.com\",\n        \"reason\": \"Read and write customers, products and prices through the Stripe API\"\n      }\n    ]\n  }\n}\n",[506,11004,11005,11009,11021,11033,11041,11048,11053,11064,11073,11078,11083,11088],{"__ignoreMap":944},[948,11006,11007],{"class":950,"line":951},[948,11008,8569],{"class":965},[948,11010,11011,11014,11016,11019],{"class":950,"line":958},[948,11012,11013],{"class":2876},"  \"message\"",[948,11015,1866],{"class":965},[948,11017,11018],{"class":979},"\"The connector requires permissions this data source was not granted. Approve them and retry.\"",[948,11020,2273],{"class":965},[948,11022,11023,11026,11028,11031],{"class":950,"line":986},[948,11024,11025],{"class":2876},"  \"code\"",[948,11027,1866],{"class":965},[948,11029,11030],{"class":979},"\"7001\"",[948,11032,2273],{"class":965},[948,11034,11035,11038],{"class":950,"line":1002},[948,11036,11037],{"class":2876},"  \"meta\"",[948,11039,11040],{"class":965},": {\n",[948,11042,11043,11046],{"class":950,"line":1009},[948,11044,11045],{"class":2876},"    \"permissions\"",[948,11047,8577],{"class":965},[948,11049,11050],{"class":950,"line":1015},[948,11051,11052],{"class":965},"      {\n",[948,11054,11055,11058,11060,11062],{"class":950,"line":1036},[948,11056,11057],{"class":2876},"        \"permission\"",[948,11059,1866],{"class":965},[948,11061,8663],{"class":979},[948,11063,2273],{"class":965},[948,11065,11066,11069,11071],{"class":950,"line":1053},[948,11067,11068],{"class":2876},"        \"reason\"",[948,11070,1866],{"class":965},[948,11072,8675],{"class":979},[948,11074,11075],{"class":950,"line":1070},[948,11076,11077],{"class":965},"      }\n",[948,11079,11080],{"class":950,"line":1075},[948,11081,11082],{"class":965},"    ]\n",[948,11084,11085],{"class":950,"line":1081},[948,11086,11087],{"class":965},"  }\n",[948,11089,11090],{"class":950,"line":1095},[948,11091,1311],{"class":965},[458,11093,11094,11095,11097,11098,11100,11101,11104,11105,476],{},"From then on, send ",[506,11096,10999],{}," back as ",[506,11099,10984],{},". The grant must equal the required set: a missing host, an empty list, and an extra host are all refused. Each refused request logs one ",[506,11102,11103],{},"deactivating the extension"," warning for that request's session, and the granted retry starts normally. See ",[467,11106,11108],{"href":11107},"\u002Freference\u002Fextensions\u002Fdata-connectors\u002Fpermissions#approve-permissions","Approve Permissions",[2450,11110,11112],{"id":11111},"test-credentials","Test Credentials",[458,11114,11115,11116,11119],{},"For a connector that takes a credential, test once with a malformed credential, once with a well-formed wrong one, and once with the real one. The real one answers ",[506,11117,11118],{},"204"," with an empty body.",[458,11121,8439,11122,11124,11125,11127],{},[506,11123,3613],{}," checks only the key's format, so it can't catch the wrong one, and only ",[506,11126,6324],{}," proves the credential. The Stripe smoke also checks that the failure doesn't echo the wrong key.",[458,11129,11130,11131,11133,11134,11136,11137,11140,11141,11144,11145,11147,11148,11150,11151,11154],{},"Without a credential, test once. A test answers ",[506,11132,11118],{}," only when your connector implements ",[506,11135,6324],{}," and it returns ",[506,11138,11139],{},"{ ok: true }",". With ",[506,11142,11143],{},"{ ok: false }",", the test fails. Without ",[506,11146,6324],{},", it answers ",[506,11149,10988],{}," code ",[506,11152,11153],{},"7002",", \"The connector does not offer a connection test\".",[458,11156,11157,11158,8409,11160,1866,11162,11164,11165,8409,11167,1866,11169,11171],{},"The malformed key fails ",[506,11159,3613],{},[506,11161,6306],{},[506,11163,10988],{}," \"The extension could not be started\", with \"Failed to connect to data source with the provided configuration\", \"Invalid extension configuration\", \"the extension is deactivated\" and your message nested inside. The wrong key fails ",[506,11166,6324],{},[506,11168,6328],{},[506,11170,10988],{}," \"Failed to connect to data source with the provided configuration\", with \"Invalid extension configuration\" and your message nested inside. Neither deactivates the data source you create afterwards, because each test starts a fresh connector.",[2450,11173,11175],{"id":11174},"create-the-data-source","Create the Data Source",[458,11177,11178,11179,11182,11183,11185],{},"Create a data source with ",[506,11180,11181],{},"POST \u002Fapi\u002F{workspace}\u002Fsources\u002Fdata",". This is the test for your declarations. Engine runs your ",[506,11184,6324],{}," first, when you implement it, so a failing connection test fails the create too.",[458,11187,11188,11189,11192,11193,11195,11196,11198],{},"A schema preview with ",[506,11190,11191],{},"POST \u002Fapi\u002F{workspace}\u002Fsources\u002Fdata\u002Fintrospect"," also runs ",[506,11194,6324],{}," first, then returns your schema without checking Engine's declaration rules. So a schema can preview cleanly and still be refused at creation. A smoke that tests, previews and creates calls ",[506,11197,6324],{}," three times, which matters when you count requests to the external data source.",[458,11200,11201,11202,11204,11205,476],{},"A refusal answers ",[506,11203,10988],{}," with a message that names the collection and what to declare. See ",[467,11206,11208],{"href":11207},"\u002Freference\u002Fextensions\u002Fdata-connectors\u002Foperations#follow-the-declaration-rules","Follow the Declaration Rules",[2450,11210,11212],{"id":11211},"check-every-relation","Check Every Relation",[458,11214,11215,11216,11219,11220,11223,11224,11227],{},"Engine narrows a relation it can't fully serve without failing creation. A direction can lose reading, or only some nested writes. Fetch the workspace's OpenAPI document with ",[506,11217,11218],{},"GET \u002Fapi\u002F{workspace}\u002Fopenapi",". Check that each relation field appears in its collection's output schema (e.g., ",[506,11221,11222],{},"pricesCollectionOutput"," lists ",[506,11225,11226],{},"product","), and that the input schemas offer the nested writes you declared for.",[458,11229,11230,11231,1181,11234,8412,11237,11240],{},"Search the Engine log for ",[506,11232,11233],{},"is not offered",[506,11235,11236],{},"offers no nested",[506,11238,11239],{},"can be neither read"," as well. The Stripe smoke also reads each relation in both directions and checks the related items.",[2450,11242,11244],{"id":11243},"exercise-rest","Exercise REST",[458,11246,11247,11248,11251],{},"Read, filter, sort, page, and include relations in both directions through ",[506,11249,11250],{},"\u002Fapi\u002F{workspace}\u002Fitems\u002F{collection}",", and compare with the external data source. Include:",[884,11253,11254,11260,11278,11285,11288,11307],{},[887,11255,11256,11257,11259],{},"a ",[506,11258,911],{}," above the external data source's page size;",[887,11261,11262,11263,11266,11267,11269,11270,11273,11274,11277],{},"a keyed read, update, and delete by ",[506,11264,11265],{},"\u002Fapi\u002F{workspace}\u002Fitems\u002F{collection}\u002F{key}",", including a ",[506,11268,4367],{}," guard that misses (your connector answers ",[506,11271,11272],{},"record: null",", the caller gets ",[506,11275,11276],{},"404"," \"No record found\", and nothing is written);",[887,11279,11280,11281,11284],{},"a create that sets a relation with ",[506,11282,11283],{},"_connect",", and one that connects an item that doesn't exist;",[887,11286,11287],{},"a write that the external data source refuses, and a write by filter that fails partway;",[887,11289,11290,11291,1181,11294,8412,11297,11300,11301,11150,11303,11306],{},"a sort field, filter operator, and total count your contract doesn't declare (for Stripe, ",[506,11292,11293],{},"sort[0][name]",[506,11295,11296],{},"filter[name][_eq]",[506,11298,11299],{},"meta[totalCount]=true","), which Engine refuses with ",[506,11302,10988],{},[506,11304,11305],{},"4003"," before your connector sees them;",[887,11308,11309,11312,11313,11315,11316,11318,11319,11150,11321,11324,11325,11327],{},[506,11310,11311],{},"_null"," on a nullable filterable field. Engine offers it only where the field declares ",[506,11314,8398],{}," and the filter declares ",[506,11317,5135],{},"; elsewhere it answers ",[506,11320,10988],{},[506,11322,11323],{},"4012"," \"Unknown input field ",[506,11326,11311],{},"\" before your connector sees it.",[458,11329,11330],{},"The Stripe smoke checks each response's status and the fields that matter. For example, it deletes three new products and one that has prices in one request, then checks what's left:",[938,11332,11334],{"className":2501,"code":11333,"filename":777,"language":2504,"meta":944,"style":944},"say \"### A bulk delete that fails partway says how far it got\"\nfor SUFFIX in a b c; do\n    req POST \"\u002Fapi\u002F$WS\u002Fitems\u002Fproducts?fields=id\" \"{\\\"id\\\":\\\"prod_docs_smoke_${STAMP}_$SUFFIX\\\",\\\"name\\\":\\\"Docs Smoke $SUFFIX\\\"}\"\n    expect 200 '.data[0].id' \"\\\"prod_docs_smoke_${STAMP}_$SUFFIX\\\"\"\ndone\nIDS=\"filter[id][_in][]=prod_docs_smoke_${STAMP}_a&filter[id][_in][]=prod_docs_smoke_${STAMP}_b&filter[id][_in][]=prod_docs_smoke_${STAMP}_c&filter[id][_in][]=prod_msfx_locked\"\nreq DELETE \"\u002Fapi\u002F$WS\u002Fitems\u002Fproducts?fields=id&$IDS\"\nexpect 422 'tostring | contains(\"Deleted 3 of 4 products before this error: This product cannot be deleted\")' 'true'\nreq GET \"\u002Fapi\u002F$WS\u002Fitems\u002Fproducts?fields=id&$IDS\"\nexpect 200 '[.data[].id]' '[\"prod_msfx_locked\"]'\n",[506,11335,11336,11343,11365,11424,11449,11454,11479,11497,11508,11525],{"__ignoreMap":944},[948,11337,11338,11340],{"class":950,"line":951},[948,11339,9143],{"class":1024},[948,11341,11342],{"class":979}," \"### A bulk delete that fails partway says how far it got\"\n",[948,11344,11345,11347,11350,11352,11355,11358,11361,11363],{"class":950,"line":958},[948,11346,1707],{"class":961},[948,11348,11349],{"class":969}," SUFFIX",[948,11351,10104],{"class":961},[948,11353,11354],{"class":979}," a",[948,11356,11357],{"class":979}," b",[948,11359,11360],{"class":979}," c",[948,11362,3391],{"class":965},[948,11364,9824],{"class":961},[948,11366,11367,11369,11371,11373,11375,11378,11380,11382,11384,11386,11388,11390,11393,11395,11398,11401,11403,11405,11407,11409,11411,11413,11415,11418,11420,11422],{"class":950,"line":986},[948,11368,10023],{"class":1024},[948,11370,9007],{"class":979},[948,11372,10736],{"class":979},[948,11374,10714],{"class":969},[948,11376,11377],{"class":979},"\u002Fitems\u002Fproducts?fields=id\"",[948,11379,9037],{"class":979},[948,11381,9040],{"class":1024},[948,11383,1920],{"class":979},[948,11385,9040],{"class":1024},[948,11387,2322],{"class":979},[948,11389,9040],{"class":1024},[948,11391,11392],{"class":979},"prod_docs_smoke_${",[948,11394,10398],{"class":969},[948,11396,11397],{"class":979},"}_",[948,11399,11400],{"class":969},"$SUFFIX",[948,11402,9040],{"class":1024},[948,11404,9074],{"class":979},[948,11406,9040],{"class":1024},[948,11408,4880],{"class":979},[948,11410,9040],{"class":1024},[948,11412,2322],{"class":979},[948,11414,9040],{"class":1024},[948,11416,11417],{"class":979},"Docs Smoke ",[948,11419,11400],{"class":969},[948,11421,9040],{"class":1024},[948,11423,10048],{"class":979},[948,11425,11426,11428,11430,11433,11435,11437,11439,11441,11443,11445,11447],{"class":950,"line":1002},[948,11427,10053],{"class":1024},[948,11429,10056],{"class":1024},[948,11431,11432],{"class":979}," '.data[0].id'",[948,11434,9010],{"class":979},[948,11436,9040],{"class":1024},[948,11438,11392],{"class":979},[948,11440,10398],{"class":969},[948,11442,11397],{"class":979},[948,11444,11400],{"class":969},[948,11446,9040],{"class":1024},[948,11448,9379],{"class":979},[948,11450,11451],{"class":950,"line":1009},[948,11452,11453],{"class":961},"done\n",[948,11455,11456,11459,11461,11464,11466,11469,11471,11474,11476],{"class":950,"line":1015},[948,11457,11458],{"class":969},"IDS",[948,11460,1775],{"class":961},[948,11462,11463],{"class":979},"\"filter[id][_in][]=prod_docs_smoke_${",[948,11465,10398],{"class":969},[948,11467,11468],{"class":979},"}_a&filter[id][_in][]=prod_docs_smoke_${",[948,11470,10398],{"class":969},[948,11472,11473],{"class":979},"}_b&filter[id][_in][]=prod_docs_smoke_${",[948,11475,10398],{"class":969},[948,11477,11478],{"class":979},"}_c&filter[id][_in][]=prod_msfx_locked\"\n",[948,11480,11481,11483,11485,11487,11489,11492,11495],{"class":950,"line":1036},[948,11482,8866],{"class":1024},[948,11484,10152],{"class":979},[948,11486,10736],{"class":979},[948,11488,10714],{"class":969},[948,11490,11491],{"class":979},"\u002Fitems\u002Fproducts?fields=id&",[948,11493,11494],{"class":969},"$IDS",[948,11496,9379],{"class":979},[948,11498,11499,11501,11503,11506],{"class":950,"line":1053},[948,11500,3133],{"class":1024},[948,11502,10780],{"class":1024},[948,11504,11505],{"class":979}," 'tostring | contains(\"Deleted 3 of 4 products before this error: This product cannot be deleted\")'",[948,11507,10910],{"class":979},[948,11509,11510,11512,11515,11517,11519,11521,11523],{"class":950,"line":1070},[948,11511,8866],{"class":1024},[948,11513,11514],{"class":979}," GET",[948,11516,10736],{"class":979},[948,11518,10714],{"class":969},[948,11520,11491],{"class":979},[948,11522,11494],{"class":969},[948,11524,9379],{"class":979},[948,11526,11527,11529,11531,11534],{"class":950,"line":1075},[948,11528,3133],{"class":1024},[948,11530,10056],{"class":1024},[948,11532,11533],{"class":979}," '[.data[].id]'",[948,11535,11536],{"class":979}," '[\"prod_msfx_locked\"]'\n",[458,11538,11539,11540,11543],{},"The response to the refused product delete is also checked for ",[506,11541,11542],{},"dashboard.stripe.com",", the domain of the log URL that names the Stripe account. The script doesn't check the other error responses for it.",[2450,11545,11547],{"id":11546},"clean-up","Clean Up",[458,11549,11550,11551,11554],{},"Delete the throwaway workspace, or the data source, at the end of every run, including failed runs. The example scripts try to delete their workspaces from an ",[506,11552,11553],{},"EXIT"," trap. Deleting a workspace doesn't delete what the run created in the external data source, so delete that in the run itself where you can. The Stripe Engine smoke deletes its customer and products in later steps, so a run that fails before them leaves them in the account.",[458,11556,11557,11558,11561],{},"When it works, every check passes and the script ends with its count (",[506,11559,11560],{},"All 47 checks passed."," for the Stripe smoke). The data source is created from a fresh workspace, and every relation you declared appears in the OpenAPI document.",[454,11563,11565],{"id":11564},"_6-break-each-check-on-purpose","6. Break Each Check on Purpose",[458,11567,11568],{},"A check that can't fail proves nothing. Right after you write a check, break the rule it covers, rebuild, rerun, and confirm that this specific check fails. Then restore the code, confirm the restore with a diff, and rebuild.",[458,11570,11571],{},"Each mutation below turns a specific check in the example connectors red:",[515,11573,11574,11587],{},[518,11575,11576],{},[521,11577,11578,11581,11584],{},[524,11579,11580],{},"Connector",[524,11582,11583],{},"Mutation",[524,11585,11586],{},"Caught by",[534,11588,11589,11614,11643,11675,11701,11722,11748,11773],{},[521,11590,11591,11594,11604],{},[539,11592,11593],{},"Stripe",[539,11595,11596,11598,11599,1153,11601,6350],{},[506,11597,4616],{}," ignores ",[506,11600,6349],{},[506,11602,11603],{},"schema.toSchemaDocument()",[539,11605,11606,11607,11610,11611,11613],{},"Two unit tests, and the Engine smoke: the read-only data source's create reaches the connector instead of failing with code ",[506,11608,11609],{},"4010",". ",[506,11612,5163],{}," and the artifact smoke pass",[521,11615,11616,11618,11627],{},[539,11617,11593],{},[539,11619,11620,11621,11624,11625],{},"The product ",[506,11622,11623],{},"create.data"," list leaves out ",[506,11626,4313],{},[539,11628,11629,11630,11633,11634,11636,11637,11150,11639,11324,11641,9160],{},"The unit test \"makes a field writable exactly when a ",[506,11631,11632],{},"data"," list names it\", and the Engine smoke: creating a product with its own ",[506,11635,1920],{}," answers ",[506,11638,10988],{},[506,11640,11305],{},[506,11642,1920],{},[521,11644,11645,11647,11662],{},[539,11646,11593],{},[539,11648,11649,11650,509,11653,11656,11657,11659,11660],{},"A rejected key (",[506,11651,11652],{},"401",[506,11654,11655],{},"403",") is thrown as ",[506,11658,6306],{}," instead of ",[506,11661,6328],{},[539,11663,11664,11665,1153,11667,11669,11670,1153,11672,11674],{},"The unit test \"reports a rejected key as AccessDenied, without Stripe's message, which echoes the key\", and the artifact smoke, which reads ",[506,11666,6309],{},[506,11668,6312],{},") instead of ",[506,11671,6331],{},[506,11673,6334],{},"). The Engine smoke can't tell the two apart: Engine reports both as \"Invalid extension configuration\"",[521,11676,11677,11680,11689],{},[539,11678,11679],{},"Art Institute of Chicago",[539,11681,11682,11685,11686,6350],{},[506,11683,11684],{},"readOne"," ignores the guard (",[506,11687,11688],{},"const filter = key;",[539,11690,11691,11692,11695,11696,11610,11698,11700],{},"The unit test \"sends the guard together with the key\", the artifact smoke, and the Engine smoke: a guard that misses answers ",[506,11693,11694],{},"200"," with the item instead of ",[506,11697,11276],{},[506,11699,5163],{}," passes",[521,11702,11703,11705,11712],{},[539,11704,11679],{},[539,11706,11707,11708,11711],{},"A descending sort without ",[506,11709,11710],{},"nulls"," puts nulls first",[539,11713,11714,11715,11717,11718,11721],{},"The unit test \"adds ",[506,11716,1920],{}," as the tie-breaker and sorts nulls last in both directions unless asked\", and the artifact smoke: a descending sort by ",[506,11719,11720],{},"dateStart"," answers nulls first instead of values",[521,11723,11724,11726,11735],{},[539,11725,11679],{},[539,11727,11728,11731,11732],{},[506,11729,11730],{},"artworks.readMany"," leaves out ",[506,11733,11734],{},"offset: paging.offset()",[539,11736,11737,11738,11741,11742,11150,11744,11324,11746,9160],{},"The Engine smoke only: an ",[506,11739,11740],{},"offset"," read answers ",[506,11743,10988],{},[506,11745,11305],{},[506,11747,11740],{},[521,11749,11750,11752,11765],{},[539,11751,11679],{},[539,11753,11754,11755,11758,11759,11761,11762],{},"The ",[506,11756,11757],{},"ConnectionFailed"," around ",[506,11760,865],{}," drops ",[506,11763,11764],{},"{ cause }",[539,11766,11767,11768,11659,11771],{},"The unit test \"keeps a permission denial as the cause, where Engine looks for it\". The artifact smoke can't catch it: it runs in Node, without network permissions. Through Engine, the denial then answers ",[506,11769,11770],{},"500",[506,11772,10988],{},[521,11774,11775,11777,11787],{},[539,11776,11679],{},[539,11778,11779,11780,11783,11784],{},"Every ",[506,11781,11782],{},"field.int32("," becomes ",[506,11785,11786],{},"field.int64(",[539,11788,11789,11790,11792,11793,11700],{},"Five unit tests, the artifact smoke and the Engine smoke: the connector checks each value against its declared type, and fails the first integer with \"The Art Institute API returned an unexpected value for ",[506,11791,1920],{},".\" ",[506,11794,5163],{},[458,11796,11797],{},"These cover a sample of the checks, not every one. Break each new check once, when you write it.",[458,11799,11800],{},"Mutations worth trying on any connector:",[884,11802,11803,11812,11822,11828,11834,11843],{},[887,11804,11805,11808,11809,11811],{},[462,11806,11807],{},"Reads:"," ignore ",[506,11810,11740],{},"; stop following the external data source's next page; reverse the order.",[887,11813,11814,11817,11818,11821],{},[462,11815,11816],{},"Filters:"," drop the re-check of returned items; treat ",[506,11819,11820],{},"not unknown"," as true; drop a null guard.",[887,11823,11824,11827],{},[462,11825,11826],{},"Pushdown:"," move a translated bound by one unit of the external data source's precision (e.g., one second).",[887,11829,11830,11833],{},[462,11831,11832],{},"Keyed operations:"," ignore the guard.",[887,11835,11836,11839,11840,11842],{},[462,11837,11838],{},"Answers:"," omit a selected field, or answer a keyed operation with ",[506,11841,6082],{},". Engine fails both, so the Engine smoke must go red.",[887,11844,11845,11848,11849,11852],{},[462,11846,11847],{},"Refusals and errors:"," accept a sort direction you don't declare; map every ",[506,11850,11851],{},"4xx"," to the same class.",[458,11854,11855],{},"When a check doesn't go red, add a wire assertion, a stand-in, or more fixture data until it does.",[458,11857,11858],{},"When it works, each mutation turns a named check red, and the restored code passes again.",[454,11860,11862],{"id":11861},"_7-run-the-ladder-twice","7. Run the Ladder Twice",[458,11864,11865],{},"Run rungs 1 to 5 twice, back to back. Residue from the first run, such as an item left by an interrupted delete or a fixture relation a test changed, can fail the second run.",[458,11867,11868],{},"Start each run by deleting leftovers from an interrupted one. The Stripe smoke deletes customers with its smoke email before it creates one:",[938,11870,11872],{"className":940,"code":11871,"filename":761,"language":943,"meta":944,"style":944},"    const email = fixtureEmail('docs-smoke');\n    for (const leftover of await stripe.list('customers', { email })) await stripe.delete(`customers\u002F${leftover.id}`);\n",[506,11873,11874,11891],{"__ignoreMap":944},[948,11875,11876,11878,11880,11882,11884,11886,11889],{"class":950,"line":951},[948,11877,1098],{"class":961},[948,11879,1741],{"class":1024},[948,11881,1028],{"class":961},[948,11883,7316],{"class":1024},[948,11885,1176],{"class":965},[948,11887,11888],{"class":979},"'docs-smoke'",[948,11890,1237],{"class":965},[948,11892,11893,11895,11897,11899,11902,11904,11906,11908,11910,11912,11914,11916,11918,11920,11923,11925,11927,11929,11932,11934,11937,11940,11942,11944,11946],{"class":950,"line":958},[948,11894,2169],{"class":961},[948,11896,1153],{"class":965},[948,11898,1399],{"class":961},[948,11900,11901],{"class":1024}," leftover",[948,11903,7363],{"class":961},[948,11905,1778],{"class":961},[948,11907,1402],{"class":969},[948,11909,476],{"class":965},[948,11911,1785],{"class":1024},[948,11913,1176],{"class":965},[948,11915,1790],{"class":979},[948,11917,1793],{"class":965},[948,11919,1796],{"class":969},[948,11921,11922],{"class":965}," })) ",[948,11924,5773],{"class":961},[948,11926,1402],{"class":969},[948,11928,476],{"class":965},[948,11930,11931],{"class":1024},"delete",[948,11933,1176],{"class":965},[948,11935,11936],{"class":979},"`customers\u002F${",[948,11938,11939],{"class":969},"leftover",[948,11941,476],{"class":979},[948,11943,1920],{"class":969},[948,11945,1756],{"class":979},[948,11947,1237],{"class":965},[458,11949,11950,11953],{},[506,11951,11952],{},"stripe.list"," reads only the first page. When leftovers can outnumber a page, follow every page.",[458,11955,11956],{},"When it works, both runs pass with the same checks and the same counts.",[454,11958,11960],{"id":11959},"_8-try-it-in-studio","8. Try It in Studio",[458,11962,11963],{},"Create the data source in Studio and use it the way your users will. No example connector automates this rung.",[11965,11966,11967,11992,11995,11998],"ol",{},[887,11968,11969,11970,11972,11973,11976,11977,11980,11981,11984,11985,5274,11988,476],{},"Create a data source from your connector: ",[462,11971,39],{}," > ",[462,11974,11975],{},"Add Source",". Enter the configuration as JSON under ",[462,11978,11979],{},"Connection Parameters"," (e.g., ",[506,11982,11983],{},"{ \"apiKey\": \"sk_test_…\" }"," for Stripe), and review the hosts Studio lists under ",[462,11986,11987],{},"Permissions",[467,11989,11991],{"href":11990},"\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Fdata-sources#add-a-data-source-in-studio","Add a Data Source in Studio",[887,11993,11994],{},"Open each collection. Browse, filter, sort, and page with every control Studio offers.",[887,11996,11997],{},"Create, edit, and delete items where the contract allows writes.",[887,11999,12000],{},"Test a data source with a wrong credential.",[458,12002,12003],{},"Studio's controls come from what Engine accepts, which includes requests your connector refuses at runtime (e.g., an unfiltered read it can't serve, or a page past a search window).",[458,12005,12006],{},"When it works, every collection loads, and every control either works or fails with a message a user can act on.",[454,12008,12010],{"id":12009},"checks-the-examples-leave-out","Checks the Examples Leave Out",[458,12012,12013],{},"The example test suites don't include these checks. Add them where your connector depends on them:",[884,12015,12016,12030,12048,12057,12063,12069],{},[887,12017,12018,12021,12022,1181,12024,12026,12027,12029],{},[462,12019,12020],{},"Live rate limits and outages."," The unit tests cover the mapping of ",[506,12023,875],{},[506,12025,879],{},", timeouts and bodies that aren't JSON with a fake ",[506,12028,865],{},". No example script triggers a rate limit or an outage against the live external data source or through Engine. To check a timeout through Engine, install a temporary build with a 1 ms deadline.",[887,12031,12032,12035,12036,12038,12039,8416,12042,12044,12045,12047],{},[462,12033,12034],{},"A body that fails while it's read."," The timeout tests make ",[506,12037,865],{}," itself reject. No test lets the headers arrive and then fails reading the body, the case the examples read inside the same ",[506,12040,12041],{},"try",[506,12043,865],{}," for. Give your fake ",[506,12046,865],{}," a response whose body stream errors, and one whose body stalls until the deadline aborts it. The smoke checks only that each request carries an abort signal.",[887,12049,12050,12053,12054,12056],{},[462,12051,12052],{},"A leaked credential in a response."," The Engine smoke redacts the configured key from every response before ",[506,12055,3133],{}," checks it, so it can't catch a response that contains the key. Check the raw responses for your secret.",[887,12058,12059,12062],{},[462,12060,12061],{},"What Engine sends, replayed."," Record the requests Engine sends your connector at rung 5, and replay them in the artifact smoke. Neither example replays them.",[887,12064,12065,12068],{},[462,12066,12067],{},"Request cost."," Count requests to the external data source per operation, and fail when an operation exceeds its budget.",[887,12070,12071,7241,12074,12077,12078,12081],{},[462,12072,12073],{},"Updates by filter.",[506,12075,12076],{},"updateMany"," is declared, but neither the unit tests nor the smokes exercise it. ",[506,12079,12080],{},"deleteMany"," is covered by a unit test and the Engine smoke.",[454,12083,12085],{"id":12084},"see-also","See Also",[884,12087,12088,12095,12100,12106,12112],{},[887,12089,12090,12094],{},[467,12091,12093],{"href":12092},"\u002Fdeveloper\u002Fextensions\u002Fdata-connectors\u002Ferrors","Handle Data Connector Errors",": map failures of the external data source to error classes, and check what callers see",[887,12096,12097,12099],{},[467,12098,246],{"href":498},": probe the external data source and decide what to declare",[887,12101,12102,12105],{},[467,12103,432],{"href":12104},"\u002Freference\u002Fextensions\u002Fdata-connectors\u002Fpermissions",": the permission handshake in detail",[887,12107,12108,12111],{},[467,12109,370],{"href":12110},"\u002Freference\u002Fextensions\u002Fcli",": build, install, and watch",[887,12113,12114,12118],{},[467,12115,12117],{"href":12116},"\u002Fguides\u002Fdata-connectors\u002Fai-agents","Build Data Connectors with AI Agents",": have a coding agent climb the ladder for you",[12120,12121,12122],"style",{},"html pre.shiki code .seNfP, html code.shiki .seNfP{--shiki-light:#64748B;--shiki-default:#64748B;--shiki-dark:#94A3B8}html pre.shiki code .sLVBU, html code.shiki .sLVBU{--shiki-light:#0891B2;--shiki-default:#0891B2;--shiki-dark:#22D3EE}html pre.shiki code .sTMul, html code.shiki .sTMul{--shiki-light:#1F2937;--shiki-default:#1F2937;--shiki-dark:#94A3B8}html pre.shiki code .sNJ9c, html code.shiki .sNJ9c{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E2E8F0}html pre.shiki code .suKVh, html code.shiki .suKVh{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#F8FAFC}html pre.shiki code .ssBNi, html code.shiki .ssBNi{--shiki-light:#1659E9;--shiki-default:#1659E9;--shiki-dark:#94B3F5}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snHjA, html code.shiki .snHjA{--shiki-light:#124BC4;--shiki-default:#124BC4;--shiki-dark:#5B8EF4}",{"title":944,"searchDepth":958,"depth":958,"links":12124},[12125,12126,12129,12130,12131,12137,12138,12146,12147,12148,12149,12150],{"id":456,"depth":958,"text":143},{"id":784,"depth":958,"text":785,"children":12127},[12128],{"id":2452,"depth":986,"text":2453},{"id":2487,"depth":958,"text":2488},{"id":5174,"depth":958,"text":558},{"id":5296,"depth":958,"text":5297,"children":12132},[12133,12134,12135,12136],{"id":6254,"depth":986,"text":6255},{"id":7232,"depth":986,"text":6388},{"id":8177,"depth":986,"text":8178},{"id":8388,"depth":986,"text":8389},{"id":8452,"depth":958,"text":8453},{"id":8711,"depth":958,"text":8712,"children":12139},[12140,12141,12142,12143,12144,12145],{"id":10974,"depth":986,"text":10975},{"id":11111,"depth":986,"text":11112},{"id":11174,"depth":986,"text":11175},{"id":11211,"depth":986,"text":11212},{"id":11243,"depth":986,"text":11244},{"id":11546,"depth":986,"text":11547},{"id":11564,"depth":958,"text":11565},{"id":11861,"depth":958,"text":11862},{"id":11959,"depth":958,"text":11960},{"id":12009,"depth":958,"text":12010},{"id":12084,"depth":958,"text":12085},"Climb the test ladder from lint and unit tests to Studio, learn which bug each rung catches, and write checks that can actually fail.","md",null,{},{"icon":259},{"title":256,"description":12151},"8YwUPKbGwj6bTnvnYcpPtB_Py9FtMTtt3u7-T3YGr5s",[12159,12161],{"title":251,"path":252,"stem":253,"description":12160,"icon":254,"children":-1},"Map each failure of an external data source to the right error class, refuse requests you can't serve, and keep secrets out of what callers see.",{"title":266,"path":267,"stem":268,"description":12162,"icon":204,"children":-1},"Configuring your Monospace environment.",1790840080107]