Skip to content

Commit af763cf

Browse files
rob-brownccclaude
andcommitted
feat: @unirate/angular v0.1.0 — Angular module for UniRate API
UniRateModule.forRoot/forRootAsync, provideUniRate() for standalone apps, Observable-based UniRateService, currencyRate and currencyConvert pipes. ng-packagr APF build (partial compilation, FESM2022). Angular 16–22. 63 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0 parents  commit af763cf

24 files changed

Lines changed: 1475 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
name: Test (Node ${{ matrix.node }})
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node: ["20", "22"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: ${{ matrix.node }}
22+
cache: npm
23+
24+
- run: npm ci --ignore-scripts
25+
26+
- run: npm audit --audit-level=high
27+
28+
- run: npm test
29+
30+
- run: npm run build

.github/workflows/release.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
release:
10+
name: Publish to npm
11+
runs-on: ubuntu-latest
12+
environment: npm
13+
permissions:
14+
contents: write
15+
id-token: write
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: "22"
23+
registry-url: "https://registry.npmjs.org"
24+
cache: npm
25+
26+
- run: npm ci --ignore-scripts
27+
28+
- run: npm audit --audit-level=high
29+
30+
- run: npm test
31+
32+
- run: npm run build
33+
34+
- run: npm publish --provenance --access public
35+
working-directory: dist
36+
env:
37+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
38+
39+
- name: Create GitHub Release
40+
env:
41+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
run: |
43+
gh release create "${{ github.ref_name }}" \
44+
--title "${{ github.ref_name }}" \
45+
--generate-notes

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
out-tsc/
4+
dist-ts/
5+
*.tsbuildinfo

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Unirate Team
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# @unirate/angular
2+
3+
Official Angular module for the [UniRate API](https://unirateapi.com) — free
4+
currency exchange rates, historical data, and VAT rates.
5+
6+
- Observable-based `UniRateService` with full API parity
7+
- `UniRateModule.forRoot()` for NgModule apps
8+
- `provideUniRate()` for standalone Angular 16+ apps
9+
- `currencyRate` and `currencyConvert` pipes (use with Angular's built-in `async` pipe)
10+
- Zero runtime dependencies (peer deps: `@angular/core`, `@angular/common`, `rxjs`)
11+
- Compiled with [ng-packagr](https://github.com/ng-packagr/ng-packagr) in Angular Package Format (APF) — AOT and Ivy compatible
12+
13+
## Install
14+
15+
```bash
16+
npm install @unirate/angular
17+
```
18+
19+
Requires Angular 16–22 and RxJS 7.
20+
21+
## Quick start
22+
23+
### Standalone app (`app.config.ts`)
24+
25+
```ts
26+
import { provideUniRate } from '@unirate/angular';
27+
28+
export const appConfig: ApplicationConfig = {
29+
providers: [
30+
provideUniRate({ apiKey: environment.UNIRATE_API_KEY }),
31+
],
32+
};
33+
```
34+
35+
### NgModule app (`app.module.ts`)
36+
37+
```ts
38+
import { UniRateModule } from '@unirate/angular';
39+
40+
@NgModule({
41+
imports: [
42+
UniRateModule.forRoot({ apiKey: environment.UNIRATE_API_KEY }),
43+
],
44+
})
45+
export class AppModule {}
46+
```
47+
48+
### Inject the service
49+
50+
```ts
51+
import { Component, inject, OnInit } from '@angular/core';
52+
import { AsyncPipe } from '@angular/common';
53+
import { Observable } from 'rxjs';
54+
import { UniRateService } from '@unirate/angular';
55+
56+
@Component({
57+
selector: 'app-rates',
58+
standalone: true,
59+
imports: [AsyncPipe],
60+
template: `<p>EUR: {{ rate$ | async }}</p>`,
61+
})
62+
export class RatesComponent implements OnInit {
63+
private rates = inject(UniRateService);
64+
rate$!: Observable<number>;
65+
66+
ngOnInit() {
67+
this.rate$ = this.rates.getRate('USD', 'EUR');
68+
}
69+
}
70+
```
71+
72+
### Use the pipes
73+
74+
```ts
75+
// In a standalone component:
76+
import { CurrencyRatePipe, CurrencyConvertPipe } from '@unirate/angular';
77+
78+
@Component({
79+
standalone: true,
80+
imports: [AsyncPipe, CurrencyRatePipe, CurrencyConvertPipe],
81+
template: `
82+
<p>1 USD = {{ 'USD' | currencyRate:'EUR' | async }} EUR</p>
83+
<p>100 USD = {{ 100 | currencyConvert:'USD':'EUR' | async }} EUR</p>
84+
`,
85+
})
86+
```
87+
88+
Or add `UniRateModule` to `imports` in an NgModule to get the pipes automatically.
89+
90+
### Async config (`forRootAsync`)
91+
92+
```ts
93+
UniRateModule.forRootAsync({
94+
useFactory: (config: ConfigService) => ({
95+
apiKey: config.getOrThrow('UNIRATE_API_KEY'),
96+
}),
97+
deps: [ConfigService],
98+
})
99+
```
100+
101+
## API
102+
103+
### `UniRateService`
104+
105+
All methods return `Observable<T>`. Subscribe with Angular's `async` pipe or `firstValueFrom()`.
106+
107+
```ts
108+
getRate(from: string, to: string): Observable<number>
109+
getRate(from: string): Observable<Record<string, number>>
110+
111+
convert(to: string, amount: number, from: string): Observable<number>
112+
113+
listCurrencies(): Observable<string[]>
114+
115+
// Pro-gated — returns 403 on the free tier
116+
getHistoricalRate(date: string, amount: number, from: string, to: string): Observable<number>
117+
getHistoricalRate(date: string, amount: number, from: string): Observable<Record<string, number>>
118+
getHistoricalRates(date: string, amount: number, base: string): Observable<Record<string, number>>
119+
convertHistorical(amount: number, from: string, to: string, date: string): Observable<number>
120+
getTimeSeries(startDate: string, endDate: string, amount: number, base: string, currencies?: string[]): Observable<Record<string, Record<string, number>>>
121+
getHistoricalLimits(): Observable<HistoricalLimitsResponse>
122+
getVATRates(): Observable<VATRatesAll>
123+
getVATRates(country: string): Observable<VATRateOne>
124+
```
125+
126+
Raw Promise-based access: `service.raw.getRate(...)` returns a `Promise<T>` from the underlying `UniRateClient`.
127+
128+
### Error handling
129+
130+
All errors extend `UniRateError`:
131+
132+
| Class | HTTP status |
133+
|---|---|
134+
| `AuthenticationError` | 401 |
135+
| `ProRequiredError` | 403 |
136+
| `InvalidCurrencyError` | 404 |
137+
| `InvalidRequestError` | 400 |
138+
| `RateLimitError` | 429 |
139+
| `UniRateError` | other / network |
140+
141+
```ts
142+
import { catchError } from 'rxjs/operators';
143+
import { UniRateError, ProRequiredError } from '@unirate/angular';
144+
145+
this.rates.getHistoricalRate('2023-01-01', 1, 'USD', 'EUR').pipe(
146+
catchError((err: UniRateError) => {
147+
if (err instanceof ProRequiredError) {
148+
console.warn('Historical rates require a Pro plan.');
149+
}
150+
throw err;
151+
}),
152+
).subscribe();
153+
```
154+
155+
## Rate limits
156+
157+
The free plan allows 1,000 requests/month. Historical endpoints (`/api/historical/*`) require a Pro subscription and return `ProRequiredError` on the free tier.
158+
159+
## Related clients
160+
161+
<!-- unirate-ecosystem-start -->
162+
| Ecosystem | Package |
163+
|---|---|
164+
| Python | [unirate-api](https://pypi.org/project/unirate-api/) |
165+
| Node.js | [unirate-api](https://www.npmjs.com/package/unirate-api) |
166+
| React | [@unirate/react](https://www.npmjs.com/package/@unirate/react) |
167+
| Vue | [@unirate/vue](https://www.npmjs.com/package/@unirate/vue) |
168+
| Next.js | [@unirate/next](https://www.npmjs.com/package/@unirate/next) |
169+
| SvelteKit | [@unirate/sveltekit](https://www.npmjs.com/package/@unirate/sveltekit) |
170+
| NestJS | [@unirate/nestjs](https://www.npmjs.com/package/@unirate/nestjs) |
171+
| Nuxt | [@unirate/nuxt](https://www.npmjs.com/package/@unirate/nuxt) |
172+
| Remix | [@unirate/remix](https://www.npmjs.com/package/@unirate/remix) |
173+
| Eleventy | [@unirate/eleventy](https://www.npmjs.com/package/@unirate/eleventy) |
174+
| Astro | [@unirate/astro](https://www.npmjs.com/package/@unirate/astro) |
175+
| Go | [unirate-api-go](https://github.com/UniRate-API/unirate-api-go) |
176+
| Rust | [unirate-api](https://crates.io/crates/unirate-api) |
177+
| Swift | [unirate-api-swift](https://github.com/UniRate-API/unirate-api-swift) |
178+
| MCP Server | [@unirate/mcp](https://www.npmjs.com/package/@unirate/mcp) |
179+
<!-- unirate-ecosystem-end -->
180+
181+
## License
182+
183+
MIT — see [LICENSE](LICENSE).

ng-package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/ng-packagr/ng-packagr/main/src/ng-package.schema.json",
3+
"lib": {
4+
"entryFile": "src/public-api.ts"
5+
},
6+
"dest": "dist"
7+
}

package.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"name": "@unirate/angular",
3+
"version": "0.1.0",
4+
"description": "Official Angular module for the UniRate currency-exchange API. UniRateModule.forRoot, provideUniRate, injectable UniRateService (Observable-based), and currencyRate / currencyConvert pipes. Zero runtime deps.",
5+
"license": "MIT",
6+
"author": "UniRate (https://unirateapi.com)",
7+
"homepage": "https://github.com/UniRate-API/angular-unirate#readme",
8+
"bugs": "https://github.com/UniRate-API/angular-unirate/issues",
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/UniRate-API/angular-unirate.git"
12+
},
13+
"keywords": [
14+
"angular",
15+
"angular-module",
16+
"unirate",
17+
"currency",
18+
"currency-converter",
19+
"exchange-rates",
20+
"forex",
21+
"fx",
22+
"money",
23+
"fintech"
24+
],
25+
"peerDependencies": {
26+
"@angular/common": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
27+
"@angular/core": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
28+
"rxjs": "^7.0.0"
29+
},
30+
"devDependencies": {
31+
"@angular/common": "^22.0.5",
32+
"@angular/compiler": "^22.0.5",
33+
"@angular/compiler-cli": "^22.0.5",
34+
"@angular/core": "^22.0.5",
35+
"@types/node": "^20.11.0",
36+
"ng-packagr": "^22.0.1",
37+
"rxjs": "^7.8.0",
38+
"tslib": "^2.6.0",
39+
"typescript": "^6.0.3",
40+
"vitest": "^4.1.7"
41+
},
42+
"scripts": {
43+
"build": "ng-packagr -p ng-package.json",
44+
"test": "vitest run",
45+
"test:watch": "vitest",
46+
"typecheck": "tsc --noEmit",
47+
"prepublishOnly": "npm run build"
48+
},
49+
"engines": {
50+
"node": ">=18.17.0"
51+
},
52+
"publishConfig": {
53+
"access": "public",
54+
"provenance": true
55+
}
56+
}

0 commit comments

Comments
 (0)