{"_id":"@data-prism/multi-api-store","_rev":"2-66a6b9f904c249673b9d301ad67e9cfc","name":"@data-prism/multi-api-store","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@data-prism/multi-api-store","version":"0.1.0","keywords":[],"author":{"name":"Jake Sower"},"_id":"@data-prism/multi-api-store@0.1.0","maintainers":[{"name":"blossomlib","email":"kind.salt4683@fastmail.com"}],"dist":{"shasum":"eeacbf0f7664d6184cacab543179403fb414b9cc","tarball":"https://registry.npmjs.org/@data-prism/multi-api-store/-/multi-api-store-0.1.0.tgz","fileCount":5,"integrity":"sha512-UDe4bpI3O7pn32mhxPgMOKKI6Q9cJjXVERE9vafZKmVi6AaCkoFLVNtuw/8fLjdjqZTo43H+zDghto2Ksgi9qg==","signatures":[{"sig":"MEUCIGko+lti1hO5ed49LKOMhea6K+LW7T6S36zfnkU/4l3JAiEAyqSIzwC2TFTIA2WwlAZdKKtJIKPLsGXXGL/U2pWCTjg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46774},"main":"./dist/index.cjs.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.esm.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.esm.js","require":"./dist/index.cjs.js"}},"gitHead":"d1b2076b5a321321302a91b81f0a3246346242c5","private":false,"scripts":{"lint":"eslint src/ test/","test":"NODE_ENV=test vitest run --coverage","build":"npm run clean && rollup -c && cp index.d.ts dist/","clean":"rm -rf dist","lint:fix":"eslint --fix src/ test/","format:fix":"prettier --write \"{src,test}/**/*.{js,jsx,ts,tsx,json,css,md}\"","test:watch":"vitest","format:check":"prettier --check \"{src,test}/**/*.{js,jsx,ts,tsx,json,css,md}\"","prepublishOnly":"npm run lint && npm run test"},"_npmUser":{"name":"blossomlib","email":"kind.salt4683@fastmail.com"},"_npmVersion":"10.9.3","description":"A Data Prism store implementation that aggregates data from multiple API endpoints into a unified interface. Supports both read and write operations (when configured with appropriate API handlers), making it perfect for querying and managing data across R","directories":{},"sideEffects":false,"_nodeVersion":"22.18.0","dependencies":{"es-toolkit":"^1.26.0","@data-prism/core":"*","json-expressions":"^0.2.1","@data-prism/utils":"*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"rollup":"^4.28.1","vitest":"^3.0.4","@rollup/plugin-json":"^6.1.0","@vitest/coverage-v8":"^3.2.4","@rollup/plugin-commonjs":"^28.0.1","@data-prism/memory-store":"*","@vitest/coverage-istanbul":"^3.0.4","@data-prism/interface-tests":"*","@rollup/plugin-node-resolve":"^16.0.0"},"_npmOperationalInternal":{"tmp":"tmp/multi-api-store_0.1.0_1757714779890_0.9338526075392837","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@data-prism/multi-api-store","version":"0.1.1","deprecated":"This package has been renamed to '@spectragraph/multi-api-store'. Please use '@spectragraph/multi-api-store' instead.","type":"module","main":"./dist/index.cjs.js","module":"./dist/index.esm.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.esm.js","require":"./dist/index.cjs.js"}},"scripts":{"build":"npm run clean && rollup -c && cp index.d.ts dist/","clean":"rm -rf dist","format:check":"prettier --check \"{src,test}/**/*.{js,jsx,ts,tsx,json,css,md}\"","format:fix":"prettier --write \"{src,test}/**/*.{js,jsx,ts,tsx,json,css,md}\"","lint":"eslint src/ test/","lint:fix":"eslint --fix src/ test/","prepublishOnly":"npm run lint && npm run test","test":"NODE_ENV=test vitest run --coverage","test:watch":"vitest"},"keywords":[],"author":{"name":"Jake Sower"},"devDependencies":{"@data-prism/interface-tests":"*","@data-prism/memory-store":"*","@rollup/plugin-commonjs":"^28.0.1","@rollup/plugin-json":"^6.1.0","@rollup/plugin-node-resolve":"^16.0.0","@vitest/coverage-istanbul":"^3.0.4","@vitest/coverage-v8":"^3.2.4","rollup":"^4.28.1","vitest":"^3.0.4"},"publishConfig":{"access":"public"},"private":false,"sideEffects":false,"dependencies":{"@data-prism/core":"*","@data-prism/utils":"*","es-toolkit":"^1.26.0","json-expressions":"^0.2.1"},"_id":"@data-prism/multi-api-store@0.1.1","gitHead":"d90da54a8b4c84ca0cde531e924f44836e9be871","description":"A Data Prism store implementation that aggregates data from multiple API endpoints into a unified interface. Supports both read and write operations (when configured with appropriate API handlers), making it perfect for querying and managing data across R","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-uY+zA9RYHnXac2hABIlgCOYDc0BO0w7xawW3HBsuVR1388oBoCWF5wHOsueMbsmiunwWgzdUqcEOHYr3qbLvYw==","shasum":"6253a8e3793c05f5bf10ea85f0436ace3dbcd860","tarball":"https://registry.npmjs.org/@data-prism/multi-api-store/-/multi-api-store-0.1.1.tgz","fileCount":2,"unpackedSize":21124,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQChqlOJw23w14m63Zues5bavITZCdh0xwAmOCkPuWmr+wIgCtcv7I4c3TA9voGoKej7caSNeK+wedChl/5J0Dm5odU="}]},"_npmUser":{"name":"blossomlib","email":"kind.salt4683@fastmail.com"},"directories":{},"maintainers":[{"name":"blossomlib","email":"kind.salt4683@fastmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/multi-api-store_0.1.1_1757801633513_0.7843252803618537"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-12T22:06:19.777Z","modified":"2025-09-13T22:13:53.917Z","0.1.0":"2025-09-12T22:06:20.084Z","0.1.1":"2025-09-13T22:13:53.718Z"},"author":{"name":"Jake Sower"},"keywords":[],"description":"A Data Prism store implementation that aggregates data from multiple API endpoints into a unified interface. Supports both read and write operations (when configured with appropriate API handlers), making it perfect for querying and managing data across R","maintainers":[{"name":"blossomlib","email":"kind.salt4683@fastmail.com"}],"readme":"# Data Prism Multi-API Store\n\nA Data Prism store implementation that aggregates data from multiple API endpoints into a unified interface. Supports both read and write operations (when configured with appropriate API handlers), making it perfect for querying and managing data across REST APIs, microservices, or third-party data sources.\n\n## Overview\n\nData Prism Multi-API Store is built around several key principles:\n\n- **Schema-driven**: Validates all operations against your Data Prism schema\n- **API-agnostic**: Works with any API that returns JSON data\n- **Full CRUD support**: Supports create, read, update, and delete operations when configured\n- **Query-compatible**: Full support for Data Prism's query language\n- **Special handlers**: Supports custom logic for complex data loading scenarios\n- **Built-in caching**: Optional caching with TTL support for improved performance\n\n## Installation\n\n```bash\nnpm install @data-prism/multi-api-store\n```\n\n## Core Concepts\n\n### Multi-API Store\n\nThe multi-API store acts as a read-only aggregation layer over multiple API endpoints. Each resource type in your schema can be configured with a getter function that fetches data from the appropriate API endpoint.\n\n```javascript\nimport { createMultiApiStore } from \"@data-prism/multi-api-store\";\nimport { defaultSelectEngine, defaultWhereEngine } from \"@data-prism/core\";\n\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async (options) => {\n\t\t\t\tconst response = await fetch(\"/api/skeptics\");\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: async (options) => {\n\t\t\t\tconst response = await fetch(\"/api/investigations\");\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t},\n\tselectEngine: defaultSelectEngine, // optional\n\twhereEngine: defaultWhereEngine, // optional\n});\n```\n\n### Operations\n\nThe multi-API store provides comprehensive data operations:\n\n- **query** - Execute Data Prism queries against the aggregated APIs\n- **create** - Create new resources using configured API handlers\n- **update** - Update existing resources using configured API handlers  \n- **delete** - Delete resources using configured API handlers\n- **upsert/merge** - Not supported (throws `StoreOperationNotSupportedError`)\n\nWrite operations (create, update, delete) are only available when the corresponding API handlers are configured for each resource type. If a handler is not provided, the operation will throw a `StoreOperationNotSupportedError`.\n\n### Special Handlers\n\nSpecial handlers allow you to customize data loading logic for complex scenarios where resources are loaded together or require conditional logic. This is particularly useful when:\n\n- One API call returns data for multiple resource types\n- You need to avoid redundant API calls when data is already loaded\n- Different contexts require different data loading strategies\n\n```javascript\nconst specialHandlers = [\n\t{\n\t\ttest: (query, context) =>\n\t\t\tquery.type === \"investigations\" &&\n\t\t\tcontext.parentQuery?.type === \"skeptics\",\n\t\thandler: async (query, context) => {\n\t\t\t// Load investigations differently when queried from skeptics\n\t\t\tconst skepticId = context.parentQuery.id;\n\t\t\tconst response = await fetch(`/api/skeptics/${skepticId}/investigations`);\n\t\t\treturn response.json();\n\t\t},\n\t},\n\t{\n\t\ttest: (query) => query.type === \"weirdBeliefs\",\n\t\thandler: (query, context) =>\n\t\t\t// If we're loading beliefs that are already embedded in investigations data\n\t\t\tcontext.parentQuery?.type === \"investigations\"\n\t\t\t\t? []\n\t\t\t\t: DEFAULT_APIS.weirdBeliefs.get(query, context),\n\t},\n];\n```\n\n### Caching\n\nThe multi-API store includes optional caching to improve performance by reducing redundant API calls:\n\n```javascript\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async () => {\n\t\t\t\tconst response = await fetch('https://api1.example.com/skeptics');\n\t\t\t\treturn response.json();\n\t\t\t}\n\t\t}\n\t},\n\tcache: {\n\t\tenabled: true,\n\t\tdefaultTTL: 5 * 60 * 1000, // 5 minutes in milliseconds\n\t\tkeyGenerator: (query, context) => {\n\t\t\t// Custom cache key generation\n\t\t\treturn `${query.type}-${JSON.stringify(query.select)}-${context.parentQuery?.type || 'root'}`;\n\t\t}\n\t}\n});\n```\n\n**Cache Configuration Options:**\n- `enabled` (boolean, default: false) - Enable or disable caching\n- `defaultTTL` (number, default: 5 minutes) - Time-to-live for cached entries in milliseconds\n- `keyGenerator` (function, optional) - Custom function to generate cache keys\n\n**Cache Behavior:**\n- Query results are cached based on the query structure and context\n- Cache is automatically cleared for a resource type when create/update/delete operations are performed\n- Expired cache entries are automatically removed on access\n- Cache keys include parent query context to handle relationship-specific caching\n\n### Middleware\n\nThe multi-API store supports middleware to enhance request processing with cross-cutting concerns like authentication, logging, and retry logic. Middleware functions are executed in order before resource handlers are called.\n\n```javascript\nimport { auth, retry, log } from \"@data-prism/multi-api-store\";\n\nconst store = createMultiApiStore(schema, {\n\tmiddleware: [\n\t\t// Add authentication headers\n\t\tauth.bearerToken(() => getAuthToken()),\n\t\t\n\t\t// Retry on server errors with exponential backoff\n\t\tretry.exponential({\n\t\t\tmaxRetries: 3,\n\t\t\ttimeout: 30000,\n\t\t}),\n\t\t\n\t\t// Log all requests and responses\n\t\tlog.requests({\n\t\t\tlogger: console,\n\t\t\tincludeTiming: true,\n\t\t}),\n\t],\n\tresources: {\n\t\t// Resource configuration...\n\t},\n});\n```\n\n**Built-in Middleware:**\n\n**Authentication (`auth`)**\n- `auth.bearerToken(getToken)` - Adds Bearer token to Authorization header\n- `auth.queryParam(getToken, paramName)` - Adds token as query parameter\n\n**Retry (`retry`)**  \n- `retry.exponential(config)` - Retries failed requests with exponential backoff\n  - Only retries 5xx server errors (not 4xx client errors)\n  - Configurable `maxRetries`, `timeout`, and `backoffFn`\n\n**Logging (`log`)**\n- `log.requests(config)` - Logs request/response details\n  - Configurable `logger`, `includeTiming` options\n\n**Custom Middleware:**\n\nMiddleware functions receive `(context, next)` parameters:\n- `context` - Request context including query, config, and request metadata\n- `next` - Function to call the next middleware or handler\n\n```javascript\nconst customAuth = (context, next) => {\n\treturn next({\n\t\t...context,\n\t\trequest: {\n\t\t\t...context.request,\n\t\t\theaders: {\n\t\t\t\t...context.request.headers,\n\t\t\t\t'X-API-Key': process.env.API_KEY,\n\t\t\t},\n\t\t},\n\t});\n};\n```\n\n### Expression Engines\n\nThe multi-API store uses focused expression engines from Data Prism Core to provide different capabilities for different query contexts:\n\n- **SELECT Engine**: Full expression capabilities including filtering, aggregations, transformations, and computed fields for SELECT clauses\n- **WHERE Engine**: Filtering-only operations for WHERE clauses, excluding expensive aggregation operations for performance and security\n\nBy default, the multi-API store uses `defaultSelectEngine` and `defaultWhereEngine` from `@data-prism/core`. You can provide custom engines in the configuration if needed for specialized use cases.\n\n## API Reference\n\n### `createMultiApiStore(schema, config)`\n\nCreates a new multi-API store instance.\n\n**Parameters:**\n\n- `schema` (Schema) - The Data Prism schema defining resource types and relationships\n- `config.resources` (object) - Configuration object mapping resource types to API handlers\n- `config.specialHandlers` (array, optional) - Array of special handler objects for custom loading logic\n- `config.cache` (object, optional) - Caching configuration options\n- `config.selectEngine` (SelectExpressionEngine, optional) - Expression engine for SELECT clauses\n- `config.whereEngine` (WhereExpressionEngine, optional) - Expression engine for WHERE clauses\n\n**Returns:** Multi-API store instance with query operations\n\n```javascript\nimport { createMultiApiStore } from \"@data-prism/multi-api-store\";\n\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async (options) => {\n\t\t\t\t// Fetch skeptics from your API\n\t\t\t\tconst response = await fetch(\"/api/skeptics\", {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: async (query, context) => {\n\t\t\t\t// Fetch investigations from a different API\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t\"https://api.sciencechecks.org/investigations\",\n\t\t\t\t\t{\n\t\t\t\t\t\theaders: { Authorization: `Bearer ${process.env.API_KEY}` },\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t\t// Note: No CUD operations configured - will throw StoreOperationNotSupportedError\n\t\t},\n\t},\n\tspecialHandlers: [\n\t\t{\n\t\t\ttest: (query, context) =>\n\t\t\t\tquery.type === \"weirdBeliefs\" &&\n\t\t\t\tcontext.parentQuery?.type === \"investigations\",\n\t\t\thandler: () => [], // Beliefs already loaded with investigations\n\t\t},\n\t],\n});\n```\n\n### Store Operations\n\n#### `store.query(query, options?, queryContext?)`\n\nExecutes a Data Prism query against the configured API endpoints.\n\n**Parameters:**\n\n- `query` (RootQuery) - The Data Prism query to execute\n- `options` (object, optional) - Additional options passed to API getter functions\n- `queryContext` (object, optional) - Query context for advanced scenarios\n\n**Returns:** Promise resolving to query results matching the query structure\n\n```javascript\nconst results = await store.query({\n\ttype: \"skeptics\",\n\tselect: {\n\t\tname: \"name\",\n\t\tspecialty: \"specialty\",\n\t\tinvestigations: {\n\t\t\tselect: [\"title\", \"conclusion\"],\n\t\t},\n\t},\n\twhere: {\n\t\tyearsActive: { $gte: 10 },\n\t},\n});\n```\n\n#### Write Operations (Create, Update, Delete)\n\nThe multi-API store supports write operations when the appropriate handlers are configured:\n\n```javascript\n// Create a new skeptic\nconst newSkeptic = await store.create({\n\ttype: \"skeptics\",\n\tattributes: {\n\t\tname: \"Neil deGrasse Tyson\",\n\t\tspecialty: \"Astrophysics and Science Communication\",\n\t\tyearsActive: 25,\n\t\tfamousQuote: \"The good thing about science is that it's true whether or not you believe in it.\"\n\t}\n});\n\n// Update an existing skeptic\nconst updatedSkeptic = await store.update({\n\ttype: \"skeptics\", \n\tid: \"james-randi\",\n\tattributes: {\n\t\tspecialty: \"Paranormal Investigation and Magic\",\n\t\tyearsActive: 52\n\t}\n});\n\n// Delete a skeptic\nconst deletedSkeptic = await store.delete({\n\ttype: \"skeptics\",\n\tid: \"james-randi\"\n});\n```\n\n#### Unsupported Operations\n\nThe following operations throw `StoreOperationNotSupportedError` when called:\n\n- `store.upsert(resource)` - Upsert operations not supported\n- `store.merge(resource)` - Merge operations not supported\n\nWrite operations (create, update, delete) will also throw `StoreOperationNotSupportedError` if the corresponding handler is not configured for the resource type.\n\n```javascript\nimport { StoreOperationNotSupportedError } from \"@data-prism/core\";\n\ntry {\n\tawait store.create({ type: \"teams\", attributes: { name: \"New Team\" } });\n} catch (error) {\n\tif (error instanceof StoreOperationNotSupportedError) {\n\t\tconsole.log(\"Write operations not supported by multi-API store\");\n\t\t// Handle gracefully - perhaps redirect to a writable store\n\t}\n}\n```\n\n## Examples\n\n### Basic Usage\n\n```javascript\nimport { createMultiApiStore } from \"@data-prism/multi-api-store\";\n\n// 1. Define your schema\nconst schema = {\n\tresources: {\n\t\tskeptics: {\n\t\t\tattributes: {\n\t\t\t\tid: { type: \"string\" },\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\tspecialty: { type: \"string\" },\n\t\t\t\tyearsActive: { type: \"number\" },\n\t\t\t},\n\t\t\trelationships: {\n\t\t\t\tinvestigations: {\n\t\t\t\t\ttype: \"investigations\",\n\t\t\t\t\tcardinality: \"many\",\n\t\t\t\t\tinverse: \"investigator\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tinvestigations: {\n\t\t\tattributes: {\n\t\t\t\tid: { type: \"string\" },\n\t\t\t\ttitle: { type: \"string\" },\n\t\t\t\tconclusion: { type: \"string\" },\n\t\t\t\tpublicationYear: { type: \"number\" },\n\t\t\t},\n\t\t\trelationships: {\n\t\t\t\tinvestigator: {\n\t\t\t\t\ttype: \"skeptics\",\n\t\t\t\t\tcardinality: \"one\",\n\t\t\t\t\tinverse: \"investigations\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n};\n\n// 2. Configure API endpoints\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async () => {\n\t\t\t\tconst response = await fetch(\"https://api1.example.com/skeptics\");\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: async () => {\n\t\t\t\tconst response = await fetch(\"https://api2.example.com/investigations\");\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t},\n});\n\n// 3. Query the aggregated data\nconst results = await store.query({\n\ttype: \"skeptics\",\n\tselect: [\"name\", \"specialty\"],\n\twhere: {\n\t\tyearsActive: { $gte: 5 },\n\t},\n});\n\nconsole.log(results);\n// [\n//   { name: \"James Randi\", specialty: \"Paranormal Investigation\" },\n//   { name: \"Michael Shermer\", specialty: \"Scientific Skepticism\" }\n// ]\n```\n\n### Advanced API Configuration with Special Handlers\n\n```javascript\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async (options) => {\n\t\t\t\t// Use options for filtering, pagination, etc.\n\t\t\t\tconst params = new URLSearchParams();\n\t\t\t\tif (options?.specialty !== undefined) {\n\t\t\t\t\tparams.append(\"specialty\", options.specialty);\n\t\t\t\t}\n\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t`https://api1.example.com/skeptics?${params}`,\n\t\t\t\t);\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Skeptics API error: ${response.statusText}`);\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: async (options) => {\n\t\t\t\t// Different API with authentication\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t\"https://api2.example.com/investigations\",\n\t\t\t\t\t{\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\tAuthorization: `Bearer ${process.env.API_TOKEN}`,\n\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\tweirdBeliefs: {\n\t\t\tget: async (options) => {\n\t\t\t\t// Third-party API\n\t\t\t\tconst response = await fetch(\"https://api3.example.com/beliefs\", {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"X-API-Key\": process.env.EXTERNAL_API_KEY,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst data = await response.json();\n\n\t\t\t\t// Transform external API format to match your schema\n\t\t\t\treturn data.claims.map((belief) => ({\n\t\t\t\t\tid: belief.beliefId,\n\t\t\t\t\tname: belief.claimName,\n\t\t\t\t\tdescription: belief.description,\n\t\t\t\t\tcategory: belief.type,\n\t\t\t\t\tbelieversCount: belief.adherents,\n\t\t\t\t\tdebunked: belief.status === \"debunked\",\n\t\t\t\t}));\n\t\t\t},\n\t\t},\n\t},\n\tspecialHandlers: [\n\t\t{\n\t\t\t// When loading investigations from skeptics, use a more efficient endpoint\n\t\t\ttest: (query, context) =>\n\t\t\t\tquery.type === \"investigations\" &&\n\t\t\t\tcontext.parentQuery?.type === \"skeptics\",\n\t\t\thandler: async (query, context) => {\n\t\t\t\tconst skepticId = context.parentQuery.id;\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t`https://api1.example.com/skeptics/${skepticId}/investigations`,\n\t\t\t\t);\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// Avoid loading beliefs if they're already included in investigation data\n\t\t\ttest: (query, context) =>\n\t\t\t\tquery.type === \"weirdBeliefs\" &&\n\t\t\t\tcontext.parentQuery?.type === \"investigations\",\n\t\t\thandler: () => [], // Return empty - beliefs already loaded with investigations\n\t\t},\n\t],\n});\n```\n\n### Working with Relationships\n\n```javascript\n// Query with relationship traversal\nconst results = await store.query({\n\ttype: \"skeptics\",\n\tselect: {\n\t\tname: \"name\",\n\t\tspecialty: \"specialty\",\n\t\tinvestigations: {\n\t\t\tselect: [\"title\", \"conclusion\"],\n\t\t\twhere: {\n\t\t\t\tpublicationYear: { $gte: 2000 },\n\t\t\t},\n\t\t},\n\t},\n\twhere: {\n\t\tyearsActive: { $gte: 10 },\n\t},\n});\n\nconsole.log(results);\n// [\n//   {\n//     name: \"James Randi\",\n//     specialty: \"Paranormal Investigation\",\n//     investigations: [\n//       { title: \"Testing Psychic Claims\", conclusion: \"No evidence found\" },\n//       { title: \"Dowsing Rod Analysis\", conclusion: \"Results no better than chance\" }\n//     ]\n//   }\n// ]\n```\n\n### Error Handling\n\n```javascript\nimport { createMultiApiStore } from \"@data-prism/multi-api-store\";\nimport {\n\tStoreOperationNotSupportedError,\n\tExpressionNotSupportedError,\n} from \"@data-prism/core\";\n\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async () => {\n\t\t\t\tconst response = await fetch(\"https://api1.example.com/skeptics\");\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`API error: ${response.statusText}`);\n\t\t\t\t}\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t},\n});\n\ntry {\n\t// This will work - query operations are supported\n\tconst skeptics = await store.query({\n\t\ttype: \"skeptics\",\n\t\tselect: [\"name\", \"specialty\"],\n\t});\n\n\t// This will throw StoreOperationNotSupportedError\n\tawait store.create({\n\t\ttype: \"skeptics\",\n\t\tattributes: { name: \"New Skeptic\", specialty: \"Critical Thinking\" },\n\t});\n} catch (error) {\n\tif (error instanceof StoreOperationNotSupportedError) {\n\t\tconsole.log(\n\t\t\t`Operation ${error.operation} not supported by ${error.storeName}`,\n\t\t);\n\t\t// Handle gracefully - perhaps use a different store for writes\n\t} else if (error instanceof ExpressionNotSupportedError) {\n\t\tconsole.log(`Expression ${error.expression} not supported`);\n\t\t// Handle gracefully - perhaps use a simpler query\n\t} else {\n\t\tconsole.error(\"API error:\", error.message);\n\t\t// Handle API errors\n\t}\n}\n```\n\n## TypeScript Support\n\nData Prism Multi-API Store includes comprehensive TypeScript definitions:\n\n```typescript\nimport type { Schema, RootQuery, QueryResult } from \"@data-prism/core\";\nimport type {\n\tMultiApiStore,\n\tMultiApiStoreConfig,\n\tApiResourceConfig,\n} from \"@data-prism/multi-api-store\";\n\nconst schema: Schema = {\n\tresources: {\n\t\tskeptics: {\n\t\t\tattributes: {\n\t\t\t\tid: { type: \"string\" },\n\t\t\t\tname: { type: \"string\" },\n\t\t\t\tspecialty: { type: \"string\" },\n\t\t\t},\n\t\t\trelationships: {},\n\t\t},\n\t},\n};\n\nconst config: MultiApiStoreConfig = {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async (): Promise<{ [key: string]: unknown }[]> => {\n\t\t\t\tconst response = await fetch(\"https://api1.example.com/skeptics\");\n\t\t\t\treturn response.json();\n\t\t\t},\n\t\t},\n\t},\n};\n\nconst store: MultiApiStore = createMultiApiStore(schema, config);\n```\n\n## Use Cases\n\n### API Gateway Pattern\n\nUse the multi-API store as a unified query layer over multiple microservices:\n\n```javascript\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: () =>\n\t\t\t\tfetch(\"https://api1.example.com/skeptics\").then((r) => r.json()),\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: () =>\n\t\t\t\tfetch(\"https://api2.example.com/investigations\").then((r) => r.json()),\n\t\t},\n\t\torganizations: {\n\t\t\tget: () =>\n\t\t\t\tfetch(\"https://api3.example.com/organizations\").then((r) => r.json()),\n\t\t},\n\t},\n});\n```\n\n### Third-Party API Integration\n\nCombine data from multiple external APIs:\n\n```javascript\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: () =>\n\t\t\t\tfetch(\"https://api1.example.com/skeptics\").then((r) => r.json()),\n\t\t},\n\t\tweirdBeliefs: {\n\t\t\tget: () =>\n\t\t\t\tfetch(`https://api4.example.com/beliefs?key=${API_KEY}`).then((r) =>\n\t\t\t\t\tr.json(),\n\t\t\t\t),\n\t\t},\n\t\tinvestigations: {\n\t\t\tget: () =>\n\t\t\t\tfetch(`https://api5.example.com/research?key=${RESEARCH_KEY}`).then(\n\t\t\t\t\t(r) => r.json(),\n\t\t\t\t),\n\t\t},\n\t},\n});\n```\n\n### Development and Testing\n\nUse as a mock data layer for development:\n\n```javascript\nconst store = createMultiApiStore(schema, {\n\tresources: {\n\t\tskeptics: {\n\t\t\tget: async () => [\n\t\t\t\t{ id: \"1\", name: \"James Randi\", specialty: \"Paranormal Investigation\" },\n\t\t\t\t{\n\t\t\t\t\tid: \"2\",\n\t\t\t\t\tname: \"Michael Shermer\",\n\t\t\t\t\tspecialty: \"Scientific Skepticism\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n});\n```\n\n## Related Packages\n\n- `@data-prism/core` - Core Data Prism functionality and types\n- `@data-prism/memory-store` - In-memory data store implementation\n- `@data-prism/postgres-store` - PostgreSQL backend\n- `@data-prism/jsonapi-store` - JSON:API client store\n","readmeFilename":"README.md"}