diff --git a/connector-registry/openweather/_meta/README.md b/connector-registry/openweather/_meta/README.md new file mode 100644 index 00000000..52386409 --- /dev/null +++ b/connector-registry/openweather/_meta/README.md @@ -0,0 +1,6 @@ +# openweather (Registry) + +This is the top-level registry entry for `openweather`. + +- Versions live under `openweather/v1` +- See author implementations under `openweather/v1/514-labs` diff --git a/connector-registry/openweather/_meta/connector.json b/connector-registry/openweather/_meta/connector.json new file mode 100644 index 00000000..e802c067 --- /dev/null +++ b/connector-registry/openweather/_meta/connector.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schemas.connector-factory.dev/connector-root.schema.json", + "identifier": "openweather", + "name": "OpenWeather", + "category": "api", + "tags": ["weather"], + "description": "Connector for OpenWeatherMap API providing current weather, forecasts, air pollution data, and geocoding services", + "homepage": "https://openweathermap.org" +} \ No newline at end of file diff --git a/connector-registry/openweather/v1/514-labs/_meta/CHANGELOG.md b/connector-registry/openweather/v1/514-labs/_meta/CHANGELOG.md new file mode 100644 index 00000000..0b9d8873 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/_meta/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this connector will be documented in this file. + +## 0.1.0 - Scaffold created + +- Initial scaffold for `openweather` (TypeScript) added. diff --git a/connector-registry/openweather/v1/514-labs/_meta/LICENSE b/connector-registry/openweather/v1/514-labs/_meta/LICENSE new file mode 100644 index 00000000..c872170b --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/_meta/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 514-labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/connector-registry/openweather/v1/514-labs/_meta/README.md b/connector-registry/openweather/v1/514-labs/_meta/README.md new file mode 100644 index 00000000..318d722c --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/_meta/README.md @@ -0,0 +1,10 @@ +# openweather Connector (by 514-labs) + +This directory contains language-agnostic metadata and documentation for the `openweather` connector. + +- Name: `openweather` +- Author: `514-labs` +- Category: `api` +- Languages: `typescript` + +See `_meta/connector.json` for connector metadata and implementation folders for language-specific code. diff --git a/connector-registry/openweather/v1/514-labs/_meta/connector.json b/connector-registry/openweather/v1/514-labs/_meta/connector.json new file mode 100644 index 00000000..0cb8af43 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/_meta/connector.json @@ -0,0 +1,22 @@ +{ + "name": "openweather", + "author": "514-labs", + "version": "v1", + "languages": [ + "typescript" + ], + "category": "api", + "capabilities": { + "extract": true, + "transform": false, + "load": false + }, + "source": { + "type": "api", + "spec": "https://openweathermap.org/api", + "homepage": "https://openweathermap.org" + }, + "tags": ["weather", "forecast", "climate", "air-pollution", "geocoding"], + "maintainers": [], + "issues": { "typescript": { "default": "" } } +} \ No newline at end of file diff --git a/connector-registry/openweather/v1/514-labs/typescript/_meta/connector.json b/connector-registry/openweather/v1/514-labs/typescript/_meta/connector.json new file mode 100644 index 00000000..4ca80e02 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/_meta/connector.json @@ -0,0 +1,8 @@ +{ + "identifier": "openweather", + "name": "openweather", + "author": "514-labs", + "version": "v1", + "language": "typescript", + "implementations": ["default"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/.env.example b/connector-registry/openweather/v1/514-labs/typescript/default/.env.example new file mode 100644 index 00000000..4aee57cd --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/.env.example @@ -0,0 +1,3 @@ +# OpenWeather API Key +# Get your API key from: https://openweathermap.org/api +OPENWEATHER_API_KEY= diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/.gitignore b/connector-registry/openweather/v1/514-labs/typescript/default/.gitignore new file mode 100644 index 00000000..deed335b --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.env diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/CONNECTOR_GUIDE.md b/connector-registry/openweather/v1/514-labs/typescript/default/CONNECTOR_GUIDE.md new file mode 100644 index 00000000..b416b321 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/CONNECTOR_GUIDE.md @@ -0,0 +1,83 @@ +# Connector Implementation Guide + +This guide walks you through implementing your `openweather` connector from scaffold to production. + +**📖 For detailed step-by-step instructions, see:** +`connector-registry/_scaffold/CONNECTOR_IMPLEMENTATION_GUIDE.md` + +Or view online: [Connector Implementation Guide](../../../../../../_scaffold/CONNECTOR_IMPLEMENTATION_GUIDE.md) + +## Quick Checklist + +### Phase 1: Understand Your API ✓ +- [ ] Read the `openweather` API documentation +- [ ] Identify authentication method +- [ ] Understand pagination pattern +- [ ] Note rate limits +- [ ] Identify key endpoints + +### Phase 2: Scaffold ✓ +- [x] Connector scaffolded + +### Phase 3: Configure Authentication +- [ ] Update `ConnectorConfig` type in `src/client/connector.ts` +- [ ] Implement `init()` method to set up auth +- [ ] Update `.env.example` with required credentials + +### Phase 4: Implement Pagination +- [ ] Choose pagination pattern (offset/cursor/page) +- [ ] Implement in `src/lib/paginate.ts` +- [ ] Test with API to verify it works + +### Phase 5: Implement Resources +- [ ] Define resource types in `src/resources/weather.ts` +- [ ] Implement list/get/create methods as needed +- [ ] Add resource getters to connector class + +### Phase 6: Add Schemas +- [ ] Create JSON schemas in `schemas/raw/json/` +- [ ] Add documentation `.md` files +- [ ] Register in `schemas/index.json` + +### Phase 7: Update Documentation +- [ ] Update `README.md` with examples +- [ ] Document config in `docs/configuration.md` +- [ ] Update `.env.example` + +### Phase 8: Write Tests +- [ ] Update `tests/resource.test.ts` +- [ ] Add observability tests if using logging/metrics +- [ ] Test pagination edge cases + +### Phase 9: Build & Test +- [ ] Run `pnpm run build` - should complete without errors +- [ ] Run `pnpm test` - all tests should pass +- [ ] Try `examples/basic-usage.ts` with real API + +### Phase 10: Real-World Testing +- [ ] Test with real credentials +- [ ] Verify pagination with large datasets +- [ ] Test error handling +- [ ] Test rate limiting (if applicable) + +## Common Patterns + +See the detailed guide for code examples of: +- Simple API key authentication +- Bearer token authentication +- OAuth2 authentication +- Offset-based pagination +- Cursor-based pagination +- Page number pagination + +## Reference Connectors + +Look at these for examples: +- **Socrata** (`connector-registry/socrata/`): Simple API with offset pagination +- **Meta Ads** (`connector-registry/meta-ads/`): OAuth2 with cursor pagination +- **Google Analytics** (`connector-registry/google-analytics/`): Service account auth + +## Need Help? + +Refer to the full implementation guide at: +`connector-registry/_scaffold/CONNECTOR_IMPLEMENTATION_GUIDE.md` diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/README.md new file mode 100644 index 00000000..fa591abd --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/README.md @@ -0,0 +1,107 @@ +# OpenWeather Connector (TypeScript) + +TypeScript connector for the OpenWeatherMap API by `514-labs`. + +## Features + +- ✅ Current weather data +- ✅ 5-day / 3-hour forecast +- ✅ Air pollution data (current, forecast, historical) +- ✅ Geocoding (direct, reverse, ZIP code) +- ✅ Multiple location formats (city name, coordinates, ZIP, city ID) +- ✅ Multiple units (metric, imperial, standard) +- ✅ Multi-language support +- ✅ Built-in logging and metrics +- ✅ TypeScript type definitions + +## Quick Start + +```typescript +import { createConnector } from '@workspace/connector-openweather' + +const conn = createConnector() +conn.init({ + apiKey: process.env.OPENWEATHER_API_KEY! +}) + +// Get current weather +const weather = await conn.weather.getCurrent({ + q: 'London,UK', + units: 'metric' +}) +console.log(`Temperature: ${weather.main.temp}°C`) +``` + +## Documentation + +- [Getting Started](docs/getting-started.md) - Quick start guide and examples +- [Configuration](docs/configuration.md) - API key setup and configuration options +- [Limits](docs/limits.md) - API rate limits and best practices +- [Observability](docs/observability.md) - Logging and metrics +- [Schemas](docs/schema.md) - Data schemas and types + +## API Resources + +### Weather +```typescript +// Current weather by city name +const weather = await conn.weather.getCurrent({ q: 'London,UK', units: 'metric' }) + +// Current weather by coordinates +const weather = await conn.weather.getCurrent({ lat: 51.5074, lon: -0.1278, units: 'metric' }) + +// Multiple cities +const data = await conn.weather.getMultipleCities([2643743, 5368361], { units: 'metric' }) +``` + +### Forecast +```typescript +// 5-day / 3-hour forecast +const forecast = await conn.forecast.get5Day3Hour({ + q: 'London,UK', + units: 'metric', + cnt: 8 // First 8 timestamps (24 hours) +}) +``` + +### Air Pollution +```typescript +// Current air quality +const current = await conn.airPollution.getCurrent({ lat: 51.5074, lon: -0.1278 }) + +// Air quality forecast +const forecast = await conn.airPollution.getForecast({ lat: 51.5074, lon: -0.1278 }) + +// Historical data (requires start and end unix timestamps) +const historical = await conn.airPollution.getHistorical({ + lat: 51.5074, + lon: -0.1278, + start: 1606223802, + end: 1606482999 +}) +``` + +### Geocoding +```typescript +// Get coordinates by city name +const locations = await conn.geocoding.getByLocationName({ q: 'London', limit: 5 }) + +// Reverse geocoding +const locations = await conn.geocoding.getByCoordinates({ lat: 51.5074, lon: -0.1278 }) + +// By ZIP code +const location = await conn.geocoding.getByZipCode({ zip: '94040,US' }) +``` + +## Requirements + +- Node.js 20+ +- OpenWeather API key (get one at [openweathermap.org](https://openweathermap.org/api)) + +## License + +MIT + +> **📖 Implementation Guide**: See `CONNECTOR_GUIDE.md` for step-by-step instructions on implementing this connector. + +Schemas: see `schemas/index.json` for machine-readable definitions and accompanying Markdown docs. diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/docs/configuration.md b/connector-registry/openweather/v1/514-labs/typescript/default/docs/configuration.md new file mode 100644 index 00000000..955cab8c --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/docs/configuration.md @@ -0,0 +1,84 @@ +# Configuration + +## Required Configuration + +### `apiKey` (required) +Your OpenWeather API key. Get one from [https://openweathermap.org/api](https://openweathermap.org/api). + +```typescript +conn.init({ + apiKey: 'your-api-key-here' +}) +``` + +## Optional Configuration + +### `baseUrl` (optional) +Override the default API base URL. Default: `https://api.openweathermap.org` + +```typescript +conn.init({ + apiKey: 'your-api-key', + baseUrl: 'https://api.openweathermap.org' // custom base URL +}) +``` + +### `logging` (optional) +Configure request/response logging. + +```typescript +conn.init({ + apiKey: 'your-api-key', + logging: { + enabled: true, + level: 'info', // 'debug' | 'info' | 'warn' | 'error' + includeQueryParams: true, + includeHeaders: false, + includeBody: false, + logger: (level, event) => console.log(level, event) // custom logger + } +}) +``` + +### `metrics` (optional) +Enable metrics collection. + +```typescript +conn.init({ + apiKey: 'your-api-key', + metrics: { + enabled: true + } +}) + +// Access metrics (if enabled) +const metricsSink = (conn as any)._metricsSink +console.log(metricsSink.events) +``` + +## Environment Variables + +Create a `.env` file in your project: + +```bash +OPENWEATHER_API_KEY=your-api-key-here +``` + +Then use it: + +```typescript +import 'dotenv/config' + +conn.init({ + apiKey: process.env.OPENWEATHER_API_KEY! +}) +``` + +## API Key Setup + +1. Sign up at [https://openweathermap.org/home/sign_up](https://openweathermap.org/home/sign_up) +2. Navigate to API keys section in your account +3. Generate a new API key +4. Wait a few hours for the key to be activated +5. Use the key in your connector configuration + diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/docs/getting-started.md b/connector-registry/openweather/v1/514-labs/typescript/default/docs/getting-started.md new file mode 100644 index 00000000..acf7b737 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/docs/getting-started.md @@ -0,0 +1,84 @@ +# Getting Started with OpenWeather Connector + +## Installation + +```bash +npm install @workspace/connector-openweather +``` + +## Quick Start + +```typescript +import { createConnector } from '@workspace/connector-openweather' + +const conn = createConnector() +conn.init({ + apiKey: process.env.OPENWEATHER_API_KEY!, + logging: { enabled: true, level: 'info' } +}) + +// Get current weather +const weather = await conn.weather.getCurrent({ + q: 'London,UK', + units: 'metric' +}) +console.log(`Temperature: ${weather.main.temp}°C`) + +// Get 5-day forecast +const forecast = await conn.forecast.get5Day3Hour({ + lat: 51.5074, + lon: -0.1278, + units: 'metric' +}) +console.log(`Forecast items: ${forecast.cnt}`) + +// Get air pollution data +const airPollution = await conn.airPollution.getCurrent({ + lat: 51.5074, + lon: -0.1278 +}) +console.log(`AQI: ${airPollution.list[0].main.aqi}`) + +// Geocoding +const locations = await conn.geocoding.getByLocationName({ + q: 'Paris,FR', + limit: 1 +}) +console.log(`Coordinates: ${locations[0].lat}, ${locations[0].lon}`) +``` + +## Available Resources + +### Weather +- `weather.getCurrent(params)` - Get current weather data +- `weather.getMultipleCities(cityIds, params)` - Get weather for multiple cities + +### Forecast +- `forecast.get5Day3Hour(params)` - Get 5 day / 3 hour forecast + +### Air Pollution +- `airPollution.getCurrent(params)` - Get current air pollution +- `airPollution.getForecast(params)` - Get air pollution forecast +- `airPollution.getHistorical(params)` - Get historical air pollution + +### Geocoding +- `geocoding.getByLocationName(params)` - Get coordinates by city name +- `geocoding.getByCoordinates(params)` - Reverse geocoding +- `geocoding.getByZipCode(params)` - Get coordinates by ZIP code + +## Location Parameters + +You can specify location in several ways: + +- **City name**: `{ q: 'London' }` or `{ q: 'London,UK' }` +- **City ID**: `{ id: 2643743 }` +- **Coordinates**: `{ lat: 51.5074, lon: -0.1278 }` +- **ZIP code**: `{ zip: '94040,US' }` + +## Units + +Choose temperature units: +- `units: 'standard'` - Kelvin (default) +- `units: 'metric'` - Celsius +- `units: 'imperial'` - Fahrenheit + diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/docs/limits.md b/connector-registry/openweather/v1/514-labs/typescript/default/docs/limits.md new file mode 100644 index 00000000..db36c144 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/docs/limits.md @@ -0,0 +1,77 @@ +# API Limits and Rate Limiting + +## OpenWeather API Rate Limits + +OpenWeather API has different rate limits depending on your subscription plan: + +### Free Plan +- **60 calls/minute** +- **1,000,000 calls/month** +- Access to current weather, forecasts, and basic features + +### Startup Plan ($40/month) +- **600 calls/minute** +- **10,000,000 calls/month** + +### Developer Plan ($160/month) +- **3,000 calls/minute** +- **100,000,000 calls/month** + +### Professional Plan (Custom) +- Custom rate limits +- Unlimited calls + +See [OpenWeather Pricing](https://openweathermap.org/price) for current details. + +## Rate Limit Headers + +OpenWeather doesn't return rate limit headers in responses. You should track your usage manually or implement client-side rate limiting. + +## Handling Rate Limits + +The connector uses the `@connector-factory/core` which supports retry and rate limiting configuration: + +```typescript +conn.init({ + apiKey: 'your-api-key', + retry: { + maxAttempts: 3, + initialDelayMs: 1000, + maxDelayMs: 10000, + backoffMultiplier: 2, + respectRetryAfter: true + }, + rateLimit: { + requestsPerSecond: 1, // For free plan: ~1 req/sec to stay under 60/min + burstCapacity: 10 + } +}) +``` + +## API Response Codes + +- **200**: Success +- **401**: Invalid API key +- **404**: Not found (invalid city name, ID, etc.) +- **429**: Too many requests (rate limit exceeded) +- **500**: Server error + +## Best Practices + +1. **Cache responses** when possible (especially for forecast data) +2. **Use City IDs** instead of city names for better accuracy and performance +3. **Batch requests** using `getMultipleCities()` when fetching multiple cities +4. **Monitor your usage** in the OpenWeather dashboard +5. **Implement exponential backoff** for retries on failures + +## Timeouts + +Default timeout is 30 seconds. You can customize it: + +```typescript +conn.init({ + apiKey: 'your-api-key', + timeoutMs: 60000 // 60 seconds +}) +``` + diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/docs/observability.md b/connector-registry/openweather/v1/514-labs/typescript/default/docs/observability.md new file mode 100644 index 00000000..664f2983 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/docs/observability.md @@ -0,0 +1,17 @@ +# Observability + +## Logging +```ts +conn.init({ + logging: { enabled: true, level: 'info', includeQueryParams: true, includeHeaders: true, includeBody: false } +}) +``` +Emits http_request and http_response with optional fields. + +## Metrics +```ts +import { createMetricsHooks, InMemoryMetricsSink } from '../src/observability/metrics-hooks' +const sink = new InMemoryMetricsSink() +conn.init({ hooks: createMetricsHooks(sink) }) +``` +Inspect sink.events for telemetry. diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/docs/schema.md b/connector-registry/openweather/v1/514-labs/typescript/default/docs/schema.md new file mode 100644 index 00000000..fd596dce --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/docs/schema.md @@ -0,0 +1,22 @@ +# Schema + +Refer to `schemas/index.json` and related files. + +## Organization + +Schemas support nested folder structures for better organization: + +- **raw/** - Raw API schemas + - `endpoints/` - API endpoint request/response schemas + - `types/` - Shared type definitions for endpoints (not shown as Files) + - `events/` - Event payloads +- **extracted/** - Normalized schemas + - `entities/` - Business entities + - `metrics/` - Aggregated data +- **files/** - File-based schemas (CSV/JSON/Parquet/Avro/NDJSON). Only items under `schemas/files` appear in the Files tab. + +## Adding Schemas + +1. Create schema files in appropriate nested folders +2. Update `schemas/index.json` with correct paths for endpoints and tables +3. Place file schemas under `schemas/files` (no index entries needed) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/examples/basic-usage.ts b/connector-registry/openweather/v1/514-labs/typescript/default/examples/basic-usage.ts new file mode 100644 index 00000000..84de4b27 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/examples/basic-usage.ts @@ -0,0 +1,79 @@ +import { createConnector } from '../src' + +async function main() { + const conn = createConnector() + conn.init({ + apiKey: process.env.OPENWEATHER_API_KEY || 'YOUR_API_KEY_HERE', + logging: { + enabled: true, + level: 'info' + } + }) + + console.log('OpenWeather Connector initialized') + + // Example: Get current weather for London + try { + const currentWeather = await conn.weather.getCurrent({ + q: 'London,UK', + units: 'metric' + }) + console.log('\n=== Current Weather in London ===') + console.log(`Temperature: ${currentWeather.main.temp}°C`) + console.log(`Feels like: ${currentWeather.main.feels_like}°C`) + console.log(`Weather: ${currentWeather.weather[0].description}`) + console.log(`Humidity: ${currentWeather.main.humidity}%`) + console.log(`Wind speed: ${currentWeather.wind.speed} m/s`) + } catch (error) { + console.error('Error fetching current weather:', error) + } + + // Example: Get 5-day forecast + try { + const forecast = await conn.forecast.get5Day3Hour({ + q: 'London,UK', + units: 'metric', + cnt: 8 // Get first 8 timestamps (24 hours) + }) + console.log('\n=== 5-Day Forecast for London (next 24h) ===') + forecast.list.forEach(item => { + console.log(`${item.dt_txt}: ${item.main.temp}°C, ${item.weather[0].description}`) + }) + } catch (error) { + console.error('Error fetching forecast:', error) + } + + // Example: Get air pollution data + try { + const airPollution = await conn.airPollution.getCurrent({ + lat: 51.5074, + lon: -0.1278 + }) + console.log('\n=== Air Quality in London ===') + const aqi = airPollution.list[0].main.aqi + const aqiLabels = ['Good', 'Fair', 'Moderate', 'Poor', 'Very Poor'] + console.log(`Air Quality Index: ${aqi} (${aqiLabels[aqi - 1]})`) + console.log(`PM2.5: ${airPollution.list[0].components.pm2_5} μg/m³`) + console.log(`PM10: ${airPollution.list[0].components.pm10} μg/m³`) + } catch (error) { + console.error('Error fetching air pollution data:', error) + } + + // Example: Geocoding - get coordinates for a city + try { + const locations = await conn.geocoding.getByLocationName({ + q: 'Paris,FR', + limit: 1 + }) + if (locations.length > 0) { + console.log('\n=== Geocoding: Paris ===') + console.log(`Location: ${locations[0].name}, ${locations[0].country}`) + console.log(`Coordinates: ${locations[0].lat}, ${locations[0].lon}`) + } + } catch (error) { + console.error('Error fetching geocoding data:', error) + } +} + +main().catch(console.error) + diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/install.config.toml b/connector-registry/openweather/v1/514-labs/typescript/default/install.config.toml new file mode 100644 index 00000000..6e79d670 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/install.config.toml @@ -0,0 +1,32 @@ +language = "typescript" +description = "TypeScript connector for openweather" +post_install_print = """ +🚀 Next steps to get your openweather connector running: + +📖 IMPORTANT: Read the implementation guide first! + $ cat CONNECTOR_GUIDE.md + Or open: connector-registry/_scaffold/CONNECTOR_IMPLEMENTATION_GUIDE.md + +📂 Go to your connector directory: + $ cd + +📦 Install dependencies: + $ npm install + +🔧 Build the connector: + $ npm run build + +🧪 Run tests: + $ npm test + +📋 Follow the checklist in CONNECTOR_GUIDE.md: + Phase 3: Configure authentication in src/client/connector.ts + Phase 4: Implement pagination in src/lib/paginate.ts + Phase 5: Implement resources in src/resources/ + Phase 6-10: Add schemas, docs, tests + +💡 Reference connectors for examples: + • connector-registry/socrata/ (simple API key + offset pagination) + • connector-registry/meta-ads/ (OAuth2 + cursor pagination) + • connector-registry/google-analytics/ (service account auth) +""" diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/jest.config.cjs b/connector-registry/openweather/v1/514-labs/typescript/default/jest.config.cjs new file mode 100644 index 00000000..9755da07 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/jest.config.cjs @@ -0,0 +1,4 @@ +module.exports = { + testEnvironment: 'node', + transform: { '^.+\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }] }, +}; diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/package.json b/connector-registry/openweather/v1/514-labs/typescript/default/package.json new file mode 100644 index 00000000..ddb5d575 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/package.json @@ -0,0 +1,23 @@ +{ + "name": "@workspace/connector-openweather", + "version": "0.0.0", + "private": true, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "test": "jest --runInBand" + }, + "engines": {"node": ">=20"}, + "dependencies": { + "@connector-factory/core": "^0.1.0" + }, + "devDependencies": { + "typescript": "^5.4.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "@types/jest": "^29.5.12", + "@types/node": "^20.11.30", + "nock": "^13.5.0" + } +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/json/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/json/README.md new file mode 100644 index 00000000..d7c9a8c9 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/json/README.md @@ -0,0 +1,15 @@ +# Extracted JSON Schemas + +This directory contains normalized/transformed JSON schemas. + +## Organization + +Like raw schemas, extracted schemas can be organized in nested folders: +- `entities/` - Normalized business entities +- `metrics/` - Calculated metrics or aggregations +- `reports/` - Structured report schemas +- Or any logical grouping for your transformed data + +## Naming Convention + +Use the same folder structure as in `raw/endpoints/` when there's a 1:1 mapping, or create new logical groupings for transformed data. diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/README.md new file mode 100644 index 00000000..7cfaa6c8 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/README.md @@ -0,0 +1,3 @@ +# Extracted relational schema + +Describe normalized tables and relationships. diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.json new file mode 100644 index 00000000..a0300447 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.json @@ -0,0 +1 @@ +{\n \"tables\": []\n}\n \ No newline at end of file diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.sql b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.sql new file mode 100644 index 00000000..7e04bb7b --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/extracted/relational/tables.sql @@ -0,0 +1 @@ +-- DDL placeholders for extracted tables diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/index.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/index.json new file mode 100644 index 00000000..681cadff --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/index.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://schemas.connector-factory.dev/schema-index.schema.json", + "version": "0.1.0", + "datasets": [ + { + "name": "current-weather", + "stage": "raw", + "kind": "endpoints", + "path": "raw/json/current-weather.schema.json", + "doc": "raw/json/current-weather.md" + }, + { + "name": "forecast", + "stage": "raw", + "kind": "endpoints", + "path": "raw/json/forecast.schema.json", + "doc": "raw/json/forecast.md" + }, + { + "name": "air-pollution", + "stage": "raw", + "kind": "endpoints", + "path": "raw/json/air-pollution.schema.json", + "doc": "raw/json/air-pollution.md" + }, + { + "name": "geocoding", + "stage": "raw", + "kind": "endpoints", + "path": "raw/json/geocoding.schema.json", + "doc": "raw/json/geocoding.md" + } + ] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/files/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/files/README.md new file mode 100644 index 00000000..44529fa5 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/files/README.md @@ -0,0 +1,18 @@ +# File schemas + +Place file-based schemas here (CSV/JSON/Parquet/Avro/NDJSON). Files in this folder are shown in the Files tab. + +Examples: +- `events.csv` +- `events.schema.json` (with title/description) + +## OpenAPI type generation + +If you have an OpenAPI spec, you can generate TypeScript types and then re-export them from your resource model. + +### Recommended: hey-api +```bash +pnpm dlx @hey-api/openapi-ts -i schemas/raw/files/openapi.json -o src/generated +``` + +After generation, use the generated types by importing them directly (for example, from `src/resources/`). diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/README.md new file mode 100644 index 00000000..ee99c545 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/README.md @@ -0,0 +1,24 @@ +# Raw JSON Schemas + +This directory contains raw JSON schemas from the source API. + +## Organization + +Schemas can be organized in nested folders for better structure: +- `endpoints/` - API endpoint request/response schemas +- `types/` - Shared type definitions +- `events/` - Event or webhook payloads +- Or any logical grouping that makes sense for your connector + +## Example Structure + +``` +json/ +├── endpoints/ +│ ├── users.schema.json +│ └── users.md +├── types/ +│ ├── User.schema.json +│ └── User.md +└── events.schema.json +``` diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.md new file mode 100644 index 00000000..6da3b656 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.md @@ -0,0 +1,72 @@ +# Air Pollution Data + +Get air quality and pollution data for any location. + +## API Endpoints + +``` +GET /data/2.5/air_pollution # Current air pollution +GET /data/2.5/air_pollution/forecast # Air pollution forecast +GET /data/2.5/air_pollution/history # Historical air pollution +``` + +## Query Parameters + +### Required +- `lat` - Latitude +- `lon` - Longitude + +### For Historical Data +- `start` - Start date (Unix timestamp, UTC) +- `end` - End date (Unix timestamp, UTC) + +## Air Quality Index (AQI) + +The API returns an Air Quality Index from 1 to 5: + +1. **Good** - Air quality is satisfactory +2. **Fair** - Air quality is acceptable +3. **Moderate** - Members of sensitive groups may experience health effects +4. **Poor** - Everyone may begin to experience health effects +5. **Very Poor** - Health alert: everyone may experience more serious health effects + +## Pollutant Components + +All concentrations are measured in **μg/m³** (micrograms per cubic meter): + +- **CO** - Carbon monoxide +- **NO** - Nitrogen monoxide +- **NO2** - Nitrogen dioxide +- **O3** - Ozone +- **SO2** - Sulphur dioxide +- **PM2.5** - Fine particles matter +- **PM10** - Coarse particulate matter +- **NH3** - Ammonia + +## Example Usage + +```typescript +// Current air pollution +const current = await conn.airPollution.getCurrent({ + lat: 51.5074, + lon: -0.1278 +}) +console.log(`AQI: ${current.list[0].main.aqi}`) +console.log(`PM2.5: ${current.list[0].components.pm2_5} μg/m³`) + +// Forecast +const forecast = await conn.airPollution.getForecast({ + lat: 51.5074, + lon: -0.1278 +}) + +// Historical (last 7 days) +const end = Math.floor(Date.now() / 1000) +const start = end - (7 * 24 * 60 * 60) +const historical = await conn.airPollution.getHistorical({ + lat: 51.5074, + lon: -0.1278, + start, + end +}) +``` diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.schema.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.schema.json new file mode 100644 index 00000000..859a3220 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/air-pollution.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Air Pollution Data", + "description": "Air pollution data from OpenWeatherMap Air Pollution API", + "type": "object", + "properties": { + "coord": { + "type": "object", + "description": "Geographic coordinates", + "properties": { + "lon": { "type": "number", "description": "Longitude" }, + "lat": { "type": "number", "description": "Latitude" } + }, + "required": ["lon", "lat"] + }, + "list": { + "type": "array", + "description": "List of air pollution data", + "items": { + "type": "object", + "properties": { + "dt": { "type": "integer", "description": "Date and time, Unix, UTC" }, + "main": { + "type": "object", + "description": "Air Quality Index", + "properties": { + "aqi": { + "type": "integer", + "description": "Air Quality Index. 1 = Good, 2 = Fair, 3 = Moderate, 4 = Poor, 5 = Very Poor", + "minimum": 1, + "maximum": 5 + } + }, + "required": ["aqi"] + }, + "components": { + "type": "object", + "description": "Air pollution components concentrations (μg/m³)", + "properties": { + "co": { "type": "number", "description": "Carbon monoxide concentration (μg/m³)" }, + "no": { "type": "number", "description": "Nitrogen monoxide concentration (μg/m³)" }, + "no2": { "type": "number", "description": "Nitrogen dioxide concentration (μg/m³)" }, + "o3": { "type": "number", "description": "Ozone concentration (μg/m³)" }, + "so2": { "type": "number", "description": "Sulphur dioxide concentration (μg/m³)" }, + "pm2_5": { "type": "number", "description": "Fine particles matter concentration (μg/m³)" }, + "pm10": { "type": "number", "description": "Coarse particulate matter concentration (μg/m³)" }, + "nh3": { "type": "number", "description": "Ammonia concentration (μg/m³)" } + }, + "required": ["co", "no", "no2", "o3", "so2", "pm2_5", "pm10", "nh3"] + } + }, + "required": ["dt", "main", "components"] + } + } + }, + "required": ["coord", "list"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.md new file mode 100644 index 00000000..37b132c2 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.md @@ -0,0 +1,44 @@ +# Current Weather Data + +Get current weather data for any location. + +## API Endpoint + +``` +GET /data/2.5/weather +``` + +## Query Parameters + +### Location (one required) +- `q` - City name (e.g., "London", "London,UK") +- `id` - City ID +- `lat` & `lon` - Geographic coordinates +- `zip` - ZIP/postal code with country code (e.g., "94040,US") + +### Optional Parameters +- `units` - Temperature units: `standard` (Kelvin), `metric` (Celsius), `imperial` (Fahrenheit) +- `lang` - Language code for descriptions (e.g., "en", "es", "fr") + +## Example Usage + +```typescript +const weather = await conn.weather.getCurrent({ + q: 'London,UK', + units: 'metric' +}) +console.log(`Temperature: ${weather.main.temp}°C`) +console.log(`Description: ${weather.weather[0].description}`) +``` + +## Response Fields + +- **coord** - Geographic coordinates +- **weather** - Array of weather conditions +- **main** - Temperature, pressure, humidity data +- **wind** - Wind speed and direction +- **clouds** - Cloudiness percentage +- **rain/snow** - Precipitation volume +- **dt** - Data calculation time (Unix timestamp) +- **sys** - Sunrise/sunset times and country code +- **name** - Location name diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.schema.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.schema.json new file mode 100644 index 00000000..83af7f1c --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/current-weather.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Current Weather Data", + "description": "Current weather data from OpenWeatherMap API", + "type": "object", + "properties": { + "coord": { + "type": "object", + "description": "Coordinates of the location", + "properties": { + "lon": { "type": "number", "description": "Longitude" }, + "lat": { "type": "number", "description": "Latitude" } + }, + "required": ["lon", "lat"] + }, + "weather": { + "type": "array", + "description": "Weather condition codes", + "items": { + "type": "object", + "properties": { + "id": { "type": "integer", "description": "Weather condition id" }, + "main": { "type": "string", "description": "Group of weather parameters (Rain, Snow, Clouds, etc.)" }, + "description": { "type": "string", "description": "Weather condition description" }, + "icon": { "type": "string", "description": "Weather icon id" } + } + } + }, + "base": { "type": "string", "description": "Internal parameter" }, + "main": { + "type": "object", + "description": "Main weather data", + "properties": { + "temp": { "type": "number", "description": "Temperature" }, + "feels_like": { "type": "number", "description": "Temperature accounting for human perception" }, + "temp_min": { "type": "number", "description": "Minimum temperature at the moment" }, + "temp_max": { "type": "number", "description": "Maximum temperature at the moment" }, + "pressure": { "type": "integer", "description": "Atmospheric pressure on the sea level, hPa" }, + "humidity": { "type": "integer", "description": "Humidity, %" }, + "sea_level": { "type": "integer", "description": "Atmospheric pressure on the sea level, hPa" }, + "grnd_level": { "type": "integer", "description": "Atmospheric pressure on the ground level, hPa" } + }, + "required": ["temp", "feels_like", "pressure", "humidity"] + }, + "visibility": { "type": "integer", "description": "Visibility, meters. Maximum 10km" }, + "wind": { + "type": "object", + "description": "Wind data", + "properties": { + "speed": { "type": "number", "description": "Wind speed. Unit Default: meter/sec" }, + "deg": { "type": "integer", "description": "Wind direction, degrees (meteorological)" }, + "gust": { "type": "number", "description": "Wind gust" } + } + }, + "clouds": { + "type": "object", + "description": "Cloudiness data", + "properties": { + "all": { "type": "integer", "description": "Cloudiness, %" } + } + }, + "rain": { + "type": "object", + "description": "Rain volume", + "properties": { + "1h": { "type": "number", "description": "Rain volume for the last 1 hour, mm" }, + "3h": { "type": "number", "description": "Rain volume for the last 3 hours, mm" } + } + }, + "snow": { + "type": "object", + "description": "Snow volume", + "properties": { + "1h": { "type": "number", "description": "Snow volume for the last 1 hour, mm" }, + "3h": { "type": "number", "description": "Snow volume for the last 3 hours, mm" } + } + }, + "dt": { "type": "integer", "description": "Time of data calculation, unix, UTC" }, + "sys": { + "type": "object", + "description": "System data", + "properties": { + "type": { "type": "integer", "description": "Internal parameter" }, + "id": { "type": "integer", "description": "Internal parameter" }, + "country": { "type": "string", "description": "Country code (GB, JP etc.)" }, + "sunrise": { "type": "integer", "description": "Sunrise time, unix, UTC" }, + "sunset": { "type": "integer", "description": "Sunset time, unix, UTC" } + }, + "required": ["country", "sunrise", "sunset"] + }, + "timezone": { "type": "integer", "description": "Shift in seconds from UTC" }, + "id": { "type": "integer", "description": "City ID" }, + "name": { "type": "string", "description": "City name" }, + "cod": { "type": "integer", "description": "Internal parameter" } + }, + "required": ["coord", "weather", "main", "dt", "sys", "id", "name", "cod"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.md new file mode 100644 index 00000000..6c60ba76 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.md @@ -0,0 +1,45 @@ +# 5 Day Weather Forecast + +Get 5-day weather forecast with data every 3 hours. + +## API Endpoint + +``` +GET /data/2.5/forecast +``` + +## Query Parameters + +### Location (one required) +- `q` - City name (e.g., "London", "London,UK") +- `id` - City ID +- `lat` & `lon` - Geographic coordinates +- `zip` - ZIP/postal code with country code + +### Optional Parameters +- `cnt` - Number of timestamps (max 40 for 5-day forecast) +- `units` - Temperature units: `standard`, `metric`, `imperial` +- `lang` - Language code + +## Example Usage + +```typescript +const forecast = await conn.forecast.get5Day3Hour({ + q: 'London,UK', + units: 'metric', + cnt: 8 // Get first 24 hours (8 * 3-hour intervals) +}) + +forecast.list.forEach(item => { + console.log(`${item.dt_txt}: ${item.main.temp}°C, ${item.weather[0].description}`) +}) +``` + +## Response Structure + +- **cnt** - Number of forecast timestamps +- **list** - Array of forecast items with 3-hour intervals + - Each item contains: temperature, weather conditions, wind, clouds, precipitation + - **dt_txt** - Forecast time in readable format + - **pop** - Probability of precipitation (0-1) +- **city** - Location information including sunrise/sunset diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.schema.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.schema.json new file mode 100644 index 00000000..ea72ccda --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/forecast.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "5 Day Weather Forecast", + "description": "5 day weather forecast with 3-hour intervals from OpenWeatherMap API", + "type": "object", + "properties": { + "cod": { "type": "string", "description": "Internal parameter" }, + "message": { "type": "number", "description": "Internal parameter" }, + "cnt": { "type": "integer", "description": "Number of timestamps returned" }, + "list": { + "type": "array", + "description": "List of forecast data", + "items": { + "type": "object", + "properties": { + "dt": { "type": "integer", "description": "Time of data forecasted, unix, UTC" }, + "main": { + "type": "object", + "properties": { + "temp": { "type": "number", "description": "Temperature" }, + "feels_like": { "type": "number", "description": "Feels like temperature" }, + "temp_min": { "type": "number", "description": "Minimum temperature" }, + "temp_max": { "type": "number", "description": "Maximum temperature" }, + "pressure": { "type": "integer", "description": "Atmospheric pressure, hPa" }, + "sea_level": { "type": "integer", "description": "Atmospheric pressure on sea level, hPa" }, + "grnd_level": { "type": "integer", "description": "Atmospheric pressure on ground level, hPa" }, + "humidity": { "type": "integer", "description": "Humidity, %" }, + "temp_kf": { "type": "number", "description": "Internal parameter" } + } + }, + "weather": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "integer", "description": "Weather condition id" }, + "main": { "type": "string", "description": "Group of weather parameters" }, + "description": { "type": "string", "description": "Weather condition description" }, + "icon": { "type": "string", "description": "Weather icon id" } + } + } + }, + "clouds": { + "type": "object", + "properties": { + "all": { "type": "integer", "description": "Cloudiness, %" } + } + }, + "wind": { + "type": "object", + "properties": { + "speed": { "type": "number", "description": "Wind speed" }, + "deg": { "type": "integer", "description": "Wind direction, degrees" }, + "gust": { "type": "number", "description": "Wind gust" } + } + }, + "visibility": { "type": "integer", "description": "Visibility, meters" }, + "pop": { "type": "number", "description": "Probability of precipitation (0 to 1)" }, + "rain": { + "type": "object", + "properties": { + "3h": { "type": "number", "description": "Rain volume for last 3 hours, mm" } + } + }, + "snow": { + "type": "object", + "properties": { + "3h": { "type": "number", "description": "Snow volume for last 3 hours, mm" } + } + }, + "sys": { + "type": "object", + "properties": { + "pod": { "type": "string", "description": "Part of day (n - night, d - day)" } + } + }, + "dt_txt": { "type": "string", "description": "Time of data forecasted, ISO format" } + } + } + }, + "city": { + "type": "object", + "description": "City information", + "properties": { + "id": { "type": "integer", "description": "City ID" }, + "name": { "type": "string", "description": "City name" }, + "coord": { + "type": "object", + "properties": { + "lat": { "type": "number", "description": "Latitude" }, + "lon": { "type": "number", "description": "Longitude" } + } + }, + "country": { "type": "string", "description": "Country code" }, + "population": { "type": "integer", "description": "City population" }, + "timezone": { "type": "integer", "description": "Shift in seconds from UTC" }, + "sunrise": { "type": "integer", "description": "Sunrise time, unix, UTC" }, + "sunset": { "type": "integer", "description": "Sunset time, unix, UTC" } + } + } + }, + "required": ["cod", "cnt", "list", "city"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.md new file mode 100644 index 00000000..ef13916d --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.md @@ -0,0 +1,82 @@ +# Geocoding API + +Convert between location names and geographic coordinates. + +## API Endpoints + +``` +GET /geo/1.0/direct # Get coordinates by location name +GET /geo/1.0/reverse # Get location name by coordinates +GET /geo/1.0/zip # Get coordinates by ZIP/postal code +``` + +## Direct Geocoding (by location name) + +Get geographic coordinates for a city, state, or country. + +### Query Parameters +- `q` - Location name (city, state, country) + - Format: `{city name}`, `{city},{state}`, `{city},{country}`, or `{city},{state},{country}` + - Example: `London`, `London,GB`, `New York,NY,US` +- `limit` - Number of results to return (max 5, default 5) + +### Example +```typescript +const locations = await conn.geocoding.getByLocationName({ + q: 'London', + limit: 5 +}) +// Returns array of locations matching "London" (London UK, London Canada, etc.) +``` + +## Reverse Geocoding (by coordinates) + +Get location information for geographic coordinates. + +### Query Parameters +- `lat` - Latitude (required) +- `lon` - Longitude (required) +- `limit` - Number of results to return (max 5, default 1) + +### Example +```typescript +const locations = await conn.geocoding.getByCoordinates({ + lat: 51.5074, + lon: -0.1278, + limit: 1 +}) +// Returns location info for these coordinates +``` + +## ZIP Code Geocoding + +Get coordinates for a ZIP/postal code. + +### Query Parameters +- `zip` - ZIP/postal code with country code + - Format: `{zip code},{country code}` + - Example: `94040,US` or `E14,GB` + +### Example +```typescript +const location = await conn.geocoding.getByZipCode({ + zip: '94040,US' +}) +// Returns: { zip: "94040", name: "Mountain View", lat: 37.3861, lon: -122.0839, country: "US" } +``` + +## Response Fields + +- **name** - Location name (city/area) +- **lat** - Latitude +- **lon** - Longitude +- **country** - Country code (ISO 3166) +- **state** - State/region (when available, especially for US locations) +- **local_names** - Object with location names in different languages (direct geocoding only) + +## Use Cases + +1. **Search for cities**: Find coordinates for weather/forecast queries +2. **Reverse lookup**: Display location name for coordinates +3. **Address validation**: Verify ZIP codes and locations exist +4. **Multi-location search**: Get multiple matches for ambiguous names diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.schema.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.schema.json new file mode 100644 index 00000000..e554551a --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/json/geocoding.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Geocoding Location", + "description": "Geographic coordinates and location data from OpenWeatherMap Geocoding API", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the location (city, state, country)" + }, + "local_names": { + "type": "object", + "description": "Location names in different languages", + "additionalProperties": { "type": "string" } + }, + "lat": { + "type": "number", + "description": "Latitude", + "minimum": -90, + "maximum": 90 + }, + "lon": { + "type": "number", + "description": "Longitude", + "minimum": -180, + "maximum": 180 + }, + "country": { + "type": "string", + "description": "Country code (ISO 3166)", + "pattern": "^[A-Z]{2}$" + }, + "state": { + "type": "string", + "description": "State name (for locations in the US)" + }, + "zip": { + "type": "string", + "description": "ZIP/postal code (only returned for ZIP code geocoding)" + } + }, + "required": ["name", "lat", "lon", "country"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/README.md b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/README.md new file mode 100644 index 00000000..48f4d13b --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/README.md @@ -0,0 +1,3 @@ +# Raw relational schema + +Describe tables and relationships. diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.json b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.json new file mode 100644 index 00000000..a0300447 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.json @@ -0,0 +1 @@ +{\n \"tables\": []\n}\n \ No newline at end of file diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.sql b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.sql new file mode 100644 index 00000000..6c1336f9 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/schemas/raw/relational/tables.sql @@ -0,0 +1 @@ +-- DDL placeholders for raw tables diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/scripts/postinstall.sh b/connector-registry/openweather/v1/514-labs/typescript/default/scripts/postinstall.sh new file mode 100644 index 00000000..076b92d2 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/scripts/postinstall.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script runs after your package is installed (pnpm/npm postinstall lifecycle). +# It is intentionally minimal and documented so you can customize it for your distribution needs. +# +# Common use cases: +# - Copy shared core sources into this package for single-file distribution +# - Prune dev-only files from the published artifact +# - Flatten directories for easier consumption +# +# By default, this script does nothing. Uncomment and adapt as needed. + +main() { + echo "postinstall: no-op (customize scripts/postinstall.sh as needed)" + + # Example: flatten src into the package root + # if [ -d src ]; then + # shopt -s dotglob + # mv src/* . || true + # shopt -u dotglob + # rm -rf src + # fi +} + +main "$@" diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/client/connector.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/client/connector.ts new file mode 100644 index 00000000..0910c2fa --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/client/connector.ts @@ -0,0 +1,112 @@ +/** + * CONNECTOR IMPLEMENTATION GUIDE + * See CONNECTOR_GUIDE.md in the root of this connector for step-by-step instructions. + * + * Quick checklist: + * [ ] Phase 3: Configure authentication (update ConnectorConfig type & init method below) + * [ ] Phase 4: Implement pagination in src/lib/paginate.ts (offset/cursor/page) + * [ ] Phase 5: Implement resources in src/resources/ + * [ ] Phase 6: Add schemas to schemas/raw/json/ + * [ ] Phase 7: Update .env.example and README.md + * [ ] Phase 8: Write tests in tests/ + * [ ] Phase 9: Build and test (pnpm run build && pnpm test) + * + * Reference connectors: + * - Simple API key: connector-registry/socrata/ + * - OAuth2: connector-registry/meta-ads/ + */ + +// TODO: Update ConnectorConfig type with your API's required fields (apiKey, domain, etc.) +// TODO: Implement init() method to transform your config to CoreConfig +// TODO: Add resource getters at the bottom of this file (get yourResource() { ... }) +import { ApiConnectorBase } from '@connector-factory/core' +import type { ConnectorConfig as CoreConfig, Hook } from '@connector-factory/core' +import { createLoggingHooks } from '../observability/logging-hooks' +import { createMetricsHooks, InMemoryMetricsSink } from '../observability/metrics-hooks' +import { createResource as createWeatherResource } from '../resources/weather' +import { createResource as createForecastResource } from '../resources/forecast' +import { createResource as createAirPollutionResource } from '../resources/air-pollution' +import { createResource as createGeocodingResource } from '../resources/geocoding' + +export type OpenWeatherConfig = { + apiKey: string + baseUrl?: string + logging?: { + enabled?: boolean + level?: 'debug'|'info'|'warn'|'error' + includeQueryParams?: boolean + includeHeaders?: boolean + includeBody?: boolean + logger?: (level: string, event: Record) => void + } + metrics?: { enabled?: boolean } + retry?: CoreConfig['retry'] + rateLimit?: CoreConfig['rateLimit'] + timeoutMs?: number +} + +export class Connector extends ApiConnectorBase { + init(userConfig: OpenWeatherConfig): this { + const baseUrl = userConfig.baseUrl ?? 'https://api.openweathermap.org' + + const coreConfig: CoreConfig = { + baseUrl, + userAgent: 'openweather-connector', + auth: { + type: 'bearer', + bearer: { token: '' } // Dummy auth; API key goes in query params + }, + defaultQueryParams: { + appid: userConfig.apiKey + }, + retry: userConfig.retry, + rateLimit: userConfig.rateLimit, + timeoutMs: userConfig.timeoutMs + } + + super.initialize(coreConfig, (cfg) => cfg) + + const cfg = (this as any).config as CoreConfig + const existing = cfg.hooks ?? {} as Partial> + + if (userConfig.logging?.enabled) { + const logging = createLoggingHooks({ + level: userConfig.logging.level, + includeQueryParams: userConfig.logging.includeQueryParams, + includeHeaders: userConfig.logging.includeHeaders, + includeBody: userConfig.logging.includeBody, + logger: userConfig.logging.logger as any + }) + cfg.hooks = { + beforeRequest: [...(existing.beforeRequest ?? []), ...(logging.beforeRequest ?? [])], + afterResponse: [...(existing.afterResponse ?? []), ...(logging.afterResponse ?? [])], + onError: [...(existing.onError ?? []), ...(logging.onError ?? [])], + onRetry: [...(existing.onRetry ?? []), ...(logging.onRetry ?? [])], + } + } + + if (userConfig.metrics?.enabled) { + const sink = new InMemoryMetricsSink() + const metrics = createMetricsHooks(sink) + const curr = cfg.hooks ?? {} + cfg.hooks = { + beforeRequest: [...(curr.beforeRequest ?? []), ...(metrics.beforeRequest ?? [])], + afterResponse: [...(curr.afterResponse ?? []), ...(metrics.afterResponse ?? [])], + onError: [...(curr.onError ?? []), ...(metrics.onError ?? [])], + onRetry: [...(curr.onRetry ?? []), ...(metrics.onRetry ?? [])], + } + ;(this as any)._metricsSink = sink + } + + return this + } + + private get sendLite() { return async (args: any) => (this as any).request(args) } + + get weather() { return createWeatherResource(this.sendLite as any) } + get forecast() { return createForecastResource(this.sendLite as any) } + get airPollution() { return createAirPollutionResource(this.sendLite as any) } + get geocoding() { return createGeocodingResource(this.sendLite as any) } +} + +export function createConnector() { return new Connector() } diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/generated/.gitkeep b/connector-registry/openweather/v1/514-labs/typescript/default/src/generated/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/index.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/index.ts new file mode 100644 index 00000000..54762dca --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/index.ts @@ -0,0 +1,8 @@ +// Main connector export +export * from './client/connector' + +// Resource types +export type { CurrentWeather, WeatherQueryParams } from './resources/weather' +export type { ForecastItem, ForecastResponse, ForecastQueryParams } from './resources/forecast' +export type { AirPollutionData, AirPollutionResponse, AirPollutionQueryParams } from './resources/air-pollution' +export type { GeocodingLocation, DirectGeocodingQueryParams, ReverseGeocodingQueryParams, ZipGeocodingQueryParams } from './resources/geocoding' diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/make-resource.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/make-resource.ts new file mode 100644 index 00000000..65fc86d2 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/make-resource.ts @@ -0,0 +1,55 @@ +import type { SendFn } from './paginate' + +/** + * Create a simple resource API at the given path. + * + * Exposes a single getAll async generator that fetches once and yields client-side chunks. + * This default suits APIs that don't expose server-side pagination. + * + * For real pagination, add a paginate helper and layer getAll on top. + */ +export function makeCrudResource< + Item, + ListParams extends Record | undefined = undefined +>( + objectPath: string, + send: SendFn, + options?: { + buildListQuery?: (params?: ListParams) => Record + } +) { + return { + async *getAll( + params?: (ListParams extends undefined + ? { pageSize?: number; maxItems?: number } + : ListParams & { pageSize?: number; maxItems?: number }) + ): AsyncGenerator> { + const listQuery = options?.buildListQuery?.(params as any) ?? {} + const res = await send({ method: 'GET', path: objectPath, query: listQuery, operation: 'getAll' }) + const data = Array.isArray(res.data) ? res.data : [] + + const pageSize = (params as any)?.pageSize as number | undefined + const maxItems = (params as any)?.maxItems as number | undefined + + let start = 0 + let remaining = typeof maxItems === 'number' ? Math.max(0, maxItems) : undefined + + if (!pageSize || pageSize <= 0) { + yield remaining !== undefined ? data.slice(0, remaining) : data + return + } + + while (start < data.length) { + const end = Math.min(data.length, start + pageSize) + let chunk = data.slice(start, end) + if (remaining !== undefined) { + if (remaining <= 0) break + if (chunk.length > remaining) chunk = chunk.slice(0, remaining) + remaining -= chunk.length + } + yield chunk + start = end + } + }, + } +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/paginate.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/paginate.ts new file mode 100644 index 00000000..f8388971 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/lib/paginate.ts @@ -0,0 +1,19 @@ +// TODO: Implement pagination for your API (see CONNECTOR_GUIDE.md Phase 4) +// TODO: Choose pattern: offset-based, cursor-based, or page-number-based +// TODO: Update query parameters to match your API ($limit/$offset, cursor, page/per_page, etc.) +export type HttpResponseEnvelope = { data: T } + +export type SendFn = (args: { + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + path: string; + query?: Record; + headers?: Record; + body?: unknown; + operation?: string; +}) => Promise> + +// Optional: cursor pagination helper (commented) +// export async function* paginateCursor(params: { +// send: SendFn; path: string; query?: Record; pageSize?: number; +// extractItems?: (res: any) => T[]; extractNextCursor?: (res: any) => string | undefined; +// }) { /* ... see docs for an example ... */ } diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/logging-hooks.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/logging-hooks.ts new file mode 100644 index 00000000..ce7c7ab9 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/logging-hooks.ts @@ -0,0 +1,107 @@ +import type { Hook, HookContext } from '@connector-factory/core' + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' +export type LoggingOptions = { + level?: LogLevel + logger?: (level: LogLevel, event: Record) => void + includeQueryParams?: boolean + includeHeaders?: boolean + includeBody?: boolean +} + +const levelOrder: Record = { debug: 10, info: 20, warn: 30, error: 40 } +const shouldLog = (cfg: LoggingOptions, level: LogLevel) => + levelOrder[level] >= levelOrder[cfg.level ?? 'info'] + +function safeUrl(raw?: string, includeQuery = false): { url?: string; query?: Record } { + if (!raw) return {} + try { + const isPathOnly = raw.startsWith('/') + const u = new URL(isPathOnly ? `https://dummy.local${raw}` : raw) + if (!includeQuery) { + return { url: isPathOnly ? u.pathname : u.toString() } + } + const query: Record = {} + u.searchParams.forEach((v, k) => { + query[k] = v + }) + return { url: isPathOnly ? `${u.pathname}${u.search}` : u.toString(), query } + } catch { + return { url: raw } + } +} + +export function createLoggingHooks( + opts: LoggingOptions = {} +): Partial<{ beforeRequest: Hook[]; afterResponse: Hook[]; onError: Hook[]; onRetry: Hook[] }> { + const logger = + opts.logger ?? ((level: LogLevel, event: Record) => { console.log(level, event) }) + + const beforeRequest: Hook = { + name: 'logging:beforeRequest', + execute: (ctx: HookContext) => { + if (ctx.type !== 'beforeRequest' || !shouldLog(opts, 'info')) return + const rawUrl = ctx.request.url || ctx.request.path + const { url, query } = safeUrl(rawUrl, !!opts.includeQueryParams) + const evt: Record = { + event: 'http_request', + operation: ctx.operation ?? ctx.request.operation, + method: ctx.request.method, + url, + } + if (opts.includeQueryParams && query) evt.query = query + if (opts.includeHeaders) evt.headers = ctx.request.headers + if (opts.includeBody && ctx.request.body !== undefined) evt.body = ctx.request.body + logger('info', evt) + }, + } + + const afterResponse: Hook = { + name: 'logging:afterResponse', + execute: (ctx: HookContext) => { + if (ctx.type !== 'afterResponse' || !shouldLog(opts, 'info')) return + const rawUrl = ctx.request.url || ctx.request.path + const { url } = safeUrl(rawUrl, !!opts.includeQueryParams) + const evt: Record = { + event: 'http_response', + operation: ctx.operation ?? ctx.request.operation, + method: ctx.request.method, + url, + status: ctx.response.status, + durationMs: ctx.response.meta?.durationMs, + retryCount: ctx.response.meta?.retryCount, + } + if (opts.includeHeaders) (evt as any).headers = (ctx.response as any).headers + if (opts.includeBody) (evt as any).body = ctx.response.data + logger('info', evt) + }, + } + + const onError: Hook = { + name: 'logging:onError', + execute: (ctx: HookContext) => { + if (ctx.type !== 'onError' || !shouldLog(opts, 'error')) return + logger('error', { + event: 'http_error', + operation: ctx.operation, + message: (ctx.error as any)?.message, + code: (ctx.error as any)?.code, + statusCode: (ctx.error as any)?.statusCode, + }) + }, + } + + const onRetry: Hook = { + name: 'logging:onRetry', + execute: (ctx: HookContext) => { + if (ctx.type !== 'onRetry' || !shouldLog(opts, 'debug')) return + logger('debug', { + event: 'http_retry', + operation: ctx.metadata.operation ?? ctx.operation, + attempt: ctx.metadata.attempt, + }) + }, + } + + return { beforeRequest: [beforeRequest], afterResponse: [afterResponse], onError: [onError], onRetry: [onRetry] } +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/metrics-hooks.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/metrics-hooks.ts new file mode 100644 index 00000000..d8200c65 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/observability/metrics-hooks.ts @@ -0,0 +1,54 @@ +import type { Hook, HookContext } from '@connector-factory/core' + +export type MetricsEvent = + | { type: 'request'; operation?: string } + | { type: 'response'; operation?: string; status?: number; durationMs?: number } + | { type: 'error'; operation?: string; code?: string } + | { type: 'retry'; operation?: string; attempt?: number } + +export interface MetricsSink { record: (event: MetricsEvent) => void } +export class InMemoryMetricsSink implements MetricsSink { + public events: MetricsEvent[] = [] + record(e: MetricsEvent) { this.events.push(e) } +} + +export function createMetricsHooks(sink: MetricsSink) { + const beforeRequest: Hook = { + name: 'metrics:beforeRequest', + execute: (ctx: HookContext) => { + if (ctx.type !== 'beforeRequest') return + sink.record({ type: 'request', operation: ctx.operation ?? ctx.request?.operation }) + }, + } + + const afterResponse: Hook = { + name: 'metrics:afterResponse', + execute: (ctx: HookContext) => { + if (ctx.type !== 'afterResponse') return + sink.record({ + type: 'response', + operation: ctx.operation ?? ctx.request?.operation, + status: ctx.response?.status, + durationMs: ctx.response?.meta?.durationMs, + }) + }, + } + + const onError: Hook = { + name: 'metrics:onError', + execute: (ctx: HookContext) => { + if (ctx.type !== 'onError') return + sink.record({ type: 'error', operation: ctx.operation, code: (ctx.error as any)?.code }) + }, + } + + const onRetry: Hook = { + name: 'metrics:onRetry', + execute: (ctx: HookContext) => { + if (ctx.type !== 'onRetry') return + sink.record({ type: 'retry', operation: ctx.metadata?.operation ?? ctx.operation, attempt: ctx.metadata?.attempt }) + }, + } + + return { beforeRequest: [beforeRequest], afterResponse: [afterResponse], onError: [onError], onRetry: [onRetry] } +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/air-pollution.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/air-pollution.ts new file mode 100644 index 00000000..99e14b34 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/air-pollution.ts @@ -0,0 +1,87 @@ +import type { SendFn } from '../lib/paginate' + +/** + * Air pollution data from OpenWeatherMap Air Pollution API + * See: https://openweathermap.org/api/air-pollution + */ +export interface AirPollutionData { + dt: number + main: { + aqi: number // Air Quality Index: 1 = Good, 2 = Fair, 3 = Moderate, 4 = Poor, 5 = Very Poor + } + components: { + co: number // Carbon monoxide (μg/m³) + no: number // Nitrogen monoxide (μg/m³) + no2: number // Nitrogen dioxide (μg/m³) + o3: number // Ozone (μg/m³) + so2: number // Sulphur dioxide (μg/m³) + pm2_5: number // Fine particles matter (μg/m³) + pm10: number // Coarse particulate matter (μg/m³) + nh3: number // Ammonia (μg/m³) + } +} + +export interface AirPollutionResponse { + coord: { + lon: number + lat: number + } + list: AirPollutionData[] +} + +export interface AirPollutionQueryParams { + lat: number // Latitude (required) + lon: number // Longitude (required) + start?: number // Start date (unix timestamp, UTC) - for historical data + end?: number // End date (unix timestamp, UTC) - for historical data +} + +export const createResource = (send: SendFn) => ({ + /** + * Get current air pollution data + * @param params - Latitude and longitude + * @returns Current air pollution data + */ + async getCurrent(params: Pick): Promise { + const response = await send({ + method: 'GET', + path: '/data/2.5/air_pollution', + query: params as any, + operation: 'airPollution.getCurrent' + }) + return response.data + }, + + /** + * Get forecast air pollution data + * @param params - Latitude and longitude + * @returns Forecasted air pollution data + */ + async getForecast(params: Pick): Promise { + const response = await send({ + method: 'GET', + path: '/data/2.5/air_pollution/forecast', + query: params as any, + operation: 'airPollution.getForecast' + }) + return response.data + }, + + /** + * Get historical air pollution data + * @param params - Latitude, longitude, start and end timestamps + * @returns Historical air pollution data + */ + async getHistorical(params: AirPollutionQueryParams): Promise { + if (!params.start || !params.end) { + throw new Error('start and end timestamps are required for historical air pollution data') + } + const response = await send({ + method: 'GET', + path: '/data/2.5/air_pollution/history', + query: params as any, + operation: 'airPollution.getHistorical' + }) + return response.data + } +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/forecast.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/forecast.ts new file mode 100644 index 00000000..4d15bf42 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/forecast.ts @@ -0,0 +1,97 @@ +import type { SendFn } from '../lib/paginate' + +/** + * Forecast data item from OpenWeatherMap 5 day / 3 hour forecast API + * See: https://openweathermap.org/forecast5 + */ +export interface ForecastItem { + dt: number + main: { + temp: number + feels_like: number + temp_min: number + temp_max: number + pressure: number + sea_level: number + grnd_level: number + humidity: number + temp_kf: number + } + weather: Array<{ + id: number + main: string + description: string + icon: string + }> + clouds: { + all: number + } + wind: { + speed: number + deg: number + gust?: number + } + visibility: number + pop: number + rain?: { + '3h'?: number + } + snow?: { + '3h'?: number + } + sys: { + pod: string + } + dt_txt: string +} + +export interface ForecastResponse { + cod: string + message: number + cnt: number + list: ForecastItem[] + city: { + id: number + name: string + coord: { + lat: number + lon: number + } + country: string + population: number + timezone: number + sunrise: number + sunset: number + } +} + +export interface ForecastQueryParams { + // Location parameters (one of these is required) + q?: string // City name, state code (US only), country code + id?: number // City ID + lat?: number // Latitude (must be used with lon) + lon?: number // Longitude (must be used with lat) + zip?: string // Zip code,country code + + // Optional parameters + cnt?: number // Number of timestamps (max 40 for 5-day forecast) + units?: 'standard' | 'metric' | 'imperial' + lang?: string +} + +export const createResource = (send: SendFn) => ({ + /** + * Get 5 day / 3 hour forecast data + * @param params - Query parameters for location and units + * @returns Forecast data for the next 5 days with 3-hour intervals + */ + async get5Day3Hour(params: ForecastQueryParams): Promise { + const response = await send({ + method: 'GET', + path: '/data/2.5/forecast', + query: params as any, + operation: 'forecast.get5Day3Hour' + }) + return response.data + } +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/geocoding.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/geocoding.ts new file mode 100644 index 00000000..bf9abb62 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/geocoding.ts @@ -0,0 +1,76 @@ +import type { SendFn } from '../lib/paginate' + +/** + * Location data from OpenWeatherMap Geocoding API + * See: https://openweathermap.org/api/geocoding-api + */ +export interface GeocodingLocation { + name: string + local_names?: Record + lat: number + lon: number + country: string + state?: string +} + +export interface ReverseGeocodingQueryParams { + lat: number // Latitude (required) + lon: number // Longitude (required) + limit?: number // Number of results (max 5, default 1) +} + +export interface DirectGeocodingQueryParams { + q: string // City name, state code, country code (e.g., "London", "London,GB") + limit?: number // Number of results (max 5, default 5) +} + +export interface ZipGeocodingQueryParams { + zip: string // Zip/post code and country code divided by comma (e.g., "94040,US") +} + +export const createResource = (send: SendFn) => ({ + /** + * Get geographic coordinates by city name + * @param params - City name and optional limit + * @returns Array of matching locations with coordinates + */ + async getByLocationName(params: DirectGeocodingQueryParams): Promise { + const response = await send({ + method: 'GET', + path: '/geo/1.0/direct', + query: params as any, + operation: 'geocoding.getByLocationName' + }) + return response.data + }, + + /** + * Get location name by geographic coordinates (reverse geocoding) + * @param params - Latitude, longitude and optional limit + * @returns Array of locations at the given coordinates + */ + async getByCoordinates(params: ReverseGeocodingQueryParams): Promise { + const response = await send({ + method: 'GET', + path: '/geo/1.0/reverse', + query: params as any, + operation: 'geocoding.getByCoordinates' + }) + return response.data + }, + + /** + * Get geographic coordinates by ZIP/postal code + * @param params - ZIP code and country code + * @returns Location data for the given ZIP code + */ + async getByZipCode(params: ZipGeocodingQueryParams): Promise { + const response = await send({ + method: 'GET', + path: '/geo/1.0/zip', + query: params as any, + operation: 'geocoding.getByZipCode' + }) + return response.data + } +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/index.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/index.ts new file mode 100644 index 00000000..07f4b32a --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/index.ts @@ -0,0 +1,4 @@ +export { createResource as createWeatherResource } from './weather' +export { createResource as createForecastResource } from './forecast' +export { createResource as createAirPollutionResource } from './air-pollution' +export { createResource as createGeocodingResource } from './geocoding' diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/weather.ts b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/weather.ts new file mode 100644 index 00000000..3fd8e7ab --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/src/resources/weather.ts @@ -0,0 +1,110 @@ +import type { SendFn } from '../lib/paginate' + +/** + * Current weather data from OpenWeatherMap API + * See: https://openweathermap.org/current + */ +export interface CurrentWeather { + coord: { + lon: number + lat: number + } + weather: Array<{ + id: number + main: string + description: string + icon: string + }> + base: string + main: { + temp: number + feels_like: number + temp_min: number + temp_max: number + pressure: number + humidity: number + sea_level?: number + grnd_level?: number + } + visibility?: number + wind: { + speed: number + deg: number + gust?: number + } + clouds: { + all: number + } + rain?: { + '1h'?: number + '3h'?: number + } + snow?: { + '1h'?: number + '3h'?: number + } + dt: number + sys: { + type?: number + id?: number + country: string + sunrise: number + sunset: number + } + timezone: number + id: number + name: string + cod: number +} + +export interface WeatherQueryParams { + // Location parameters (one of these is required) + q?: string // City name, state code (US only), country code (e.g., "London", "London,UK") + id?: number // City ID + lat?: number // Latitude (must be used with lon) + lon?: number // Longitude (must be used with lat) + zip?: string // Zip code,country code (e.g., "94040,US") + + // Optional parameters + units?: 'standard' | 'metric' | 'imperial' // Temperature units (default: standard/Kelvin) + lang?: string // Language code (e.g., 'en', 'es', 'fr') +} + +export const createResource = (send: SendFn) => ({ + /** + * Get current weather data by location + * @param params - Query parameters for location and units + * @returns Current weather data + */ + async getCurrent(params: WeatherQueryParams): Promise { + const response = await send({ + method: 'GET', + path: '/data/2.5/weather', + query: params as any, + operation: 'weather.getCurrent' + }) + return response.data + }, + + /** + * Get current weather for multiple cities by IDs + * @param cityIds - Array of city IDs + * @param params - Optional query parameters for units and language + * @returns Object containing list of current weather data + */ + async getMultipleCities( + cityIds: number[], + params?: Pick + ): Promise<{ cnt: number; list: CurrentWeather[] }> { + const response = await send<{ cnt: number; list: CurrentWeather[] }>({ + method: 'GET', + path: '/data/2.5/group', + query: { + id: cityIds.join(','), + ...params + }, + operation: 'weather.getMultipleCities' + }) + return response.data + } +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/tests/observability.test.ts b/connector-registry/openweather/v1/514-labs/typescript/default/tests/observability.test.ts new file mode 100644 index 00000000..45250197 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/tests/observability.test.ts @@ -0,0 +1,41 @@ +/* eslint-env jest */ +// @ts-nocheck +import nock from 'nock' +import { createConnector } from '../src' + +it('logging includes url, headers, body when enabled', async () => { + const BASE = 'https://api.openweathermap.org' + const API_KEY = 'test-key' + + nock(BASE) + .get('/data/2.5/weather') + .query({ q: 'London', appid: API_KEY }) + .reply(200, { name: 'London', main: { temp: 15 } }, { 'x-test-header': 'ok' }) + + const events: Array<{ level: string; event: Record }> = [] + const conn = createConnector() + conn.init({ + apiKey: API_KEY, + logging: { + enabled: true, + level: 'info', + includeQueryParams: true, + includeHeaders: true, + includeBody: true, + logger: (level, event) => events.push({ level, event }) + } + }) + + await conn.weather.getCurrent({ q: 'London' }) + + const reqEvt = events.find(e => (e.event as any)?.event === 'http_request')?.event as any + const respEvt = events.find(e => (e.event as any)?.event === 'http_response')?.event as any + + expect(reqEvt).toBeDefined() + expect(respEvt).toBeDefined() + expect(String(respEvt.url)).toContain('/data/2.5/weather') + expect(typeof reqEvt.headers).toBe('object') + expect((respEvt.headers ?? {})['x-test-header']).toBe('ok') + expect(respEvt.body).toBeDefined() + expect(respEvt.body.name).toBe('London') +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/tests/resource.test.ts b/connector-registry/openweather/v1/514-labs/typescript/default/tests/resource.test.ts new file mode 100644 index 00000000..51ae0883 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/tests/resource.test.ts @@ -0,0 +1,267 @@ +/* eslint-env jest */ +// @ts-nocheck +import nock from 'nock' +import { createConnector } from '../src' + +describe('OpenWeather Connector', () => { + const BASE = 'https://api.openweathermap.org' + const API_KEY = 'test-api-key' + + afterEach(() => { + nock.cleanAll() + }) + + describe('weather resource', () => { + it('gets current weather by city name', async () => { + const mockResponse = { + coord: { lon: -0.1257, lat: 51.5085 }, + weather: [{ id: 800, main: 'Clear', description: 'clear sky', icon: '01d' }], + base: 'stations', + main: { temp: 15, feels_like: 14, temp_min: 13, temp_max: 17, pressure: 1013, humidity: 72 }, + visibility: 10000, + wind: { speed: 3.6, deg: 230 }, + clouds: { all: 0 }, + dt: 1605182400, + sys: { type: 1, id: 1414, country: 'GB', sunrise: 1605168123, sunset: 1605201456 }, + timezone: 0, + id: 2643743, + name: 'London', + cod: 200 + } + + nock(BASE) + .get('/data/2.5/weather') + .query({ q: 'London,UK', units: 'metric', appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const weather = await conn.weather.getCurrent({ q: 'London,UK', units: 'metric' }) + + expect(weather.name).toBe('London') + expect(weather.main.temp).toBe(15) + expect(weather.weather[0].main).toBe('Clear') + }) + + it('gets current weather by coordinates', async () => { + const mockResponse = { + coord: { lon: -0.1278, lat: 51.5074 }, + weather: [{ id: 801, main: 'Clouds', description: 'few clouds', icon: '02d' }], + main: { temp: 16, feels_like: 15, temp_min: 14, temp_max: 18, pressure: 1015, humidity: 65 }, + name: 'London', + cod: 200 + } + + nock(BASE) + .get('/data/2.5/weather') + .query({ lat: 51.5074, lon: -0.1278, appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const weather = await conn.weather.getCurrent({ lat: 51.5074, lon: -0.1278 }) + + expect(weather.name).toBe('London') + expect(weather.main.temp).toBe(16) + }) + + it('gets weather for multiple cities', async () => { + const mockResponse = { + cnt: 2, + list: [ + { id: 2643743, name: 'London', main: { temp: 15 } }, + { id: 5368361, name: 'Los Angeles', main: { temp: 22 } } + ] + } + + nock(BASE) + .get('/data/2.5/group') + .query({ id: '2643743,5368361', units: 'metric', appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const data = await conn.weather.getMultipleCities([2643743, 5368361], { units: 'metric' }) + + expect(data.cnt).toBe(2) + expect(data.list).toHaveLength(2) + expect(data.list[0].name).toBe('London') + }) + }) + + describe('forecast resource', () => { + it('gets 5-day forecast', async () => { + const mockResponse = { + cod: '200', + message: 0, + cnt: 2, + list: [ + { + dt: 1605189600, + main: { temp: 15, feels_like: 14, temp_min: 15, temp_max: 16, pressure: 1013, humidity: 72, temp_kf: 0 }, + weather: [{ id: 800, main: 'Clear', description: 'clear sky', icon: '01d' }], + clouds: { all: 0 }, + wind: { speed: 3.6, deg: 230 }, + visibility: 10000, + pop: 0, + sys: { pod: 'd' }, + dt_txt: '2020-11-12 12:00:00' + }, + { + dt: 1605200400, + main: { temp: 14, feels_like: 13, temp_min: 14, temp_max: 14, pressure: 1014, humidity: 75, temp_kf: 0 }, + weather: [{ id: 801, main: 'Clouds', description: 'few clouds', icon: '02n' }], + clouds: { all: 20 }, + wind: { speed: 3.1, deg: 240 }, + visibility: 10000, + pop: 0, + sys: { pod: 'n' }, + dt_txt: '2020-11-12 15:00:00' + } + ], + city: { + id: 2643743, + name: 'London', + coord: { lat: 51.5085, lon: -0.1257 }, + country: 'GB', + population: 1000000, + timezone: 0, + sunrise: 1605168123, + sunset: 1605201456 + } + } + + nock(BASE) + .get('/data/2.5/forecast') + .query({ q: 'London,UK', cnt: 2, appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const forecast = await conn.forecast.get5Day3Hour({ q: 'London,UK', cnt: 2 }) + + expect(forecast.cnt).toBe(2) + expect(forecast.list).toHaveLength(2) + expect(forecast.city.name).toBe('London') + expect(forecast.list[0].main.temp).toBe(15) + }) + }) + + describe('air pollution resource', () => { + it('gets current air pollution', async () => { + const mockResponse = { + coord: { lon: -0.1278, lat: 51.5074 }, + list: [{ + dt: 1605182400, + main: { aqi: 2 }, + components: { + co: 230.31, + no: 0.21, + no2: 12.34, + o3: 56.78, + so2: 5.67, + pm2_5: 8.9, + pm10: 12.3, + nh3: 0.45 + } + }] + } + + nock(BASE) + .get('/data/2.5/air_pollution') + .query({ lat: 51.5074, lon: -0.1278, appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const pollution = await conn.airPollution.getCurrent({ lat: 51.5074, lon: -0.1278 }) + + expect(pollution.list).toHaveLength(1) + expect(pollution.list[0].main.aqi).toBe(2) + expect(pollution.list[0].components.pm2_5).toBe(8.9) + }) + }) + + describe('geocoding resource', () => { + it('gets coordinates by location name', async () => { + const mockResponse = [ + { + name: 'London', + local_names: { en: 'London' }, + lat: 51.5074, + lon: -0.1278, + country: 'GB', + state: 'England' + } + ] + + nock(BASE) + .get('/geo/1.0/direct') + .query({ q: 'London', limit: 1, appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const locations = await conn.geocoding.getByLocationName({ q: 'London', limit: 1 }) + + expect(locations).toHaveLength(1) + expect(locations[0].name).toBe('London') + expect(locations[0].country).toBe('GB') + expect(locations[0].lat).toBe(51.5074) + }) + + it('gets location by coordinates (reverse geocoding)', async () => { + const mockResponse = [ + { + name: 'London', + lat: 51.5074, + lon: -0.1278, + country: 'GB' + } + ] + + nock(BASE) + .get('/geo/1.0/reverse') + .query({ lat: 51.5074, lon: -0.1278, limit: 1, appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const locations = await conn.geocoding.getByCoordinates({ lat: 51.5074, lon: -0.1278, limit: 1 }) + + expect(locations).toHaveLength(1) + expect(locations[0].name).toBe('London') + }) + + it('gets location by ZIP code', async () => { + const mockResponse = { + zip: '94040', + name: 'Mountain View', + lat: 37.3861, + lon: -122.0839, + country: 'US' + } + + nock(BASE) + .get('/geo/1.0/zip') + .query({ zip: '94040,US', appid: API_KEY }) + .reply(200, mockResponse) + + const conn = createConnector() + conn.init({ apiKey: API_KEY }) + + const location = await conn.geocoding.getByZipCode({ zip: '94040,US' }) + + expect(location.name).toBe('Mountain View') + expect(location.country).toBe('US') + expect(location.lat).toBe(37.3861) + }) + }) +}) diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.json b/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.json new file mode 100644 index 00000000..920e22a7 --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "Node", + "declaration": true, + "outDir": "dist", + "rootDir": ".", + "strict": true, + "skipLibCheck": true, + "types": ["node", "jest"], + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "examples", "tests"] +} diff --git a/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.test.json b/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.test.json new file mode 100644 index 00000000..2c9aa02f --- /dev/null +++ b/connector-registry/openweather/v1/514-labs/typescript/default/tsconfig.test.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": ".temp-test", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src", + "tests" + ] +} diff --git a/connector-registry/openweather/v1/_meta/version.json b/connector-registry/openweather/v1/_meta/version.json new file mode 100644 index 00000000..ac61e797 --- /dev/null +++ b/connector-registry/openweather/v1/_meta/version.json @@ -0,0 +1,7 @@ +{ + "name": "openweather", + "version": "v1", + "status": "beta", + "releasedAt": "", + "notes": "openweather v1 version" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e983559..af5ededc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -290,6 +290,31 @@ importers: specifier: ^5.6.3 version: 5.8.3 + connector-registry/openweather/v1/514-labs/typescript/default: + dependencies: + '@connector-factory/core': + specifier: workspace:* + version: link:../../../../../../packages/core + devDependencies: + '@types/jest': + specifier: ^29.5.12 + version: 29.5.14 + '@types/node': + specifier: ^20.11.30 + version: 20.16.13 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.16.13)(ts-node@10.9.2(@swc/core@1.10.15(@swc/helpers@0.5.15))(@types/node@20.16.13)(typescript@5.8.3)) + nock: + specifier: ^13.5.0 + version: 13.5.6 + ts-jest: + specifier: ^29.1.2 + version: 29.4.4(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.16.13)(ts-node@10.9.2(@swc/core@1.10.15(@swc/helpers@0.5.15))(@types/node@20.16.13)(typescript@5.8.3)))(typescript@5.8.3) + typescript: + specifier: ^5.4.0 + version: 5.8.3 + examples/quickstart: dependencies: dotenv: @@ -8410,7 +8435,7 @@ snapshots: '@babel/traverse': 7.24.5 '@babel/types': 7.24.5 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8428,7 +8453,7 @@ snapshots: dependencies: '@babel/compat-data': 7.24.4 '@babel/helper-validator-option': 7.23.5 - browserslist: 4.24.0 + browserslist: 4.26.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -8581,7 +8606,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.5 '@babel/parser': 7.24.5 '@babel/types': 7.24.5 - debug: 4.3.7 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -13118,7 +13143,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.7 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -13772,7 +13797,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.7 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -14848,7 +14873,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.7 + debug: 4.4.3 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -15003,7 +15028,7 @@ snapshots: nock@13.5.6: dependencies: - debug: 4.3.7 + debug: 4.4.3 json-stringify-safe: 5.0.1 propagate: 2.0.1 transitivePeerDependencies: