@pagopa/dx-savemoney 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/__tests__/finding.test.js +22 -0
- package/dist/__tests__/finding.test.js.map +1 -1
- package/dist/azure/__tests__/analyzer-tags.test.js +28 -1
- package/dist/azure/__tests__/analyzer-tags.test.js.map +1 -1
- package/dist/azure/__tests__/config.test.js +31 -1
- package/dist/azure/__tests__/config.test.js.map +1 -1
- package/dist/azure/__tests__/report.test.js +71 -0
- package/dist/azure/__tests__/report.test.js.map +1 -1
- package/dist/azure/analyzer.d.ts +8 -1
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +57 -41
- package/dist/azure/analyzer.js.map +1 -1
- package/dist/azure/analyzers/registry.d.ts.map +1 -1
- package/dist/azure/analyzers/registry.js +4 -4
- package/dist/azure/analyzers/registry.js.map +1 -1
- package/dist/azure/analyzers/types.d.ts +6 -0
- package/dist/azure/analyzers/types.d.ts.map +1 -1
- package/dist/azure/config.d.ts.map +1 -1
- package/dist/azure/config.js +1 -5
- package/dist/azure/config.js.map +1 -1
- package/dist/azure/pricing/__tests__/cache.test.d.ts +8 -0
- package/dist/azure/pricing/__tests__/cache.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/cache.test.js +56 -0
- package/dist/azure/pricing/__tests__/cache.test.js.map +1 -0
- package/dist/azure/pricing/__tests__/client.test.d.ts +9 -0
- package/dist/azure/pricing/__tests__/client.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/client.test.js +181 -0
- package/dist/azure/pricing/__tests__/client.test.js.map +1 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +6 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.js +283 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.js.map +1 -0
- package/dist/azure/pricing/cache.d.ts +45 -0
- package/dist/azure/pricing/cache.d.ts.map +1 -0
- package/dist/azure/pricing/cache.js +79 -0
- package/dist/azure/pricing/cache.js.map +1 -0
- package/dist/azure/pricing/client.d.ts +67 -0
- package/dist/azure/pricing/client.d.ts.map +1 -0
- package/dist/azure/pricing/client.js +96 -0
- package/dist/azure/pricing/client.js.map +1 -0
- package/dist/azure/pricing/index.d.ts +6 -0
- package/dist/azure/pricing/index.d.ts.map +1 -0
- package/dist/azure/pricing/index.js +6 -0
- package/dist/azure/pricing/index.js.map +1 -0
- package/dist/azure/pricing/pricing-service.d.ts +46 -0
- package/dist/azure/pricing/pricing-service.d.ts.map +1 -0
- package/dist/azure/pricing/pricing-service.js +84 -0
- package/dist/azure/pricing/pricing-service.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +137 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js +160 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +113 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +9 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js +204 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/app-service.d.ts +27 -0
- package/dist/azure/pricing/resolvers/app-service.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/app-service.js +67 -0
- package/dist/azure/pricing/resolvers/app-service.js.map +1 -0
- package/dist/azure/pricing/resolvers/disk.d.ts +59 -0
- package/dist/azure/pricing/resolvers/disk.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/disk.js +140 -0
- package/dist/azure/pricing/resolvers/disk.js.map +1 -0
- package/dist/azure/pricing/resolvers/index.d.ts +12 -0
- package/dist/azure/pricing/resolvers/index.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/index.js +8 -0
- package/dist/azure/pricing/resolvers/index.js.map +1 -0
- package/dist/azure/pricing/resolvers/public-ip.d.ts +32 -0
- package/dist/azure/pricing/resolvers/public-ip.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/public-ip.js +56 -0
- package/dist/azure/pricing/resolvers/public-ip.js.map +1 -0
- package/dist/azure/pricing/resolvers/vm.d.ts +51 -0
- package/dist/azure/pricing/resolvers/vm.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/vm.js +89 -0
- package/dist/azure/pricing/resolvers/vm.js.map +1 -0
- package/dist/azure/pricing/schema.d.ts +88 -0
- package/dist/azure/pricing/schema.d.ts.map +1 -0
- package/dist/azure/pricing/schema.js +64 -0
- package/dist/azure/pricing/schema.js.map +1 -0
- package/dist/azure/pricing/test-cache-dir.d.ts +3 -0
- package/dist/azure/pricing/test-cache-dir.d.ts.map +1 -0
- package/dist/azure/pricing/test-cache-dir.js +17 -0
- package/dist/azure/pricing/test-cache-dir.js.map +1 -0
- package/dist/azure/report.d.ts.map +1 -1
- package/dist/azure/report.js +57 -18
- package/dist/azure/report.js.map +1 -1
- package/dist/azure/resources/__tests__/app-service.test.d.ts +5 -0
- package/dist/azure/resources/__tests__/app-service.test.d.ts.map +1 -0
- package/dist/azure/resources/__tests__/app-service.test.js +105 -0
- package/dist/azure/resources/__tests__/app-service.test.js.map +1 -0
- package/dist/azure/resources/app-service.d.ts +8 -3
- package/dist/azure/resources/app-service.d.ts.map +1 -1
- package/dist/azure/resources/app-service.js +35 -2
- package/dist/azure/resources/app-service.js.map +1 -1
- package/dist/azure/resources/disk.d.ts +3 -1
- package/dist/azure/resources/disk.d.ts.map +1 -1
- package/dist/azure/resources/disk.js +35 -3
- package/dist/azure/resources/disk.js.map +1 -1
- package/dist/azure/resources/public-ip.d.ts +3 -1
- package/dist/azure/resources/public-ip.d.ts.map +1 -1
- package/dist/azure/resources/public-ip.js +41 -3
- package/dist/azure/resources/public-ip.js.map +1 -1
- package/dist/azure/resources/vm.d.ts +4 -1
- package/dist/azure/resources/vm.d.ts.map +1 -1
- package/dist/azure/resources/vm.js +49 -6
- package/dist/azure/resources/vm.js.map +1 -1
- package/dist/azure/types.d.ts +18 -3
- package/dist/azure/types.d.ts.map +1 -1
- package/dist/finding.d.ts +3 -3
- package/dist/finding.d.ts.map +1 -1
- package/dist/finding.js +7 -0
- package/dist/finding.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +5 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +5 -3
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/finding.test.ts +24 -0
- package/src/azure/__tests__/analyzer-tags.test.ts +36 -1
- package/src/azure/__tests__/config.test.ts +43 -1
- package/src/azure/__tests__/fixtures/full-override.yaml +2 -0
- package/src/azure/__tests__/report.test.ts +78 -0
- package/src/azure/analyzer.ts +70 -52
- package/src/azure/analyzers/registry.ts +8 -2
- package/src/azure/analyzers/types.ts +6 -0
- package/src/azure/config.ts +1 -5
- package/src/azure/pricing/__tests__/cache.test.ts +67 -0
- package/src/azure/pricing/__tests__/client.test.ts +212 -0
- package/src/azure/pricing/__tests__/fixtures/vm-b2s-westeurope.json +51 -0
- package/src/azure/pricing/__tests__/pricing-service.test.ts +315 -0
- package/src/azure/pricing/cache.ts +92 -0
- package/src/azure/pricing/client.ts +137 -0
- package/src/azure/pricing/index.ts +6 -0
- package/src/azure/pricing/pricing-service.ts +125 -0
- package/src/azure/pricing/resolvers/__tests__/app-service.test.ts +154 -0
- package/src/azure/pricing/resolvers/__tests__/disk.test.ts +189 -0
- package/src/azure/pricing/resolvers/__tests__/public-ip.test.ts +130 -0
- package/src/azure/pricing/resolvers/__tests__/vm.test.ts +232 -0
- package/src/azure/pricing/resolvers/app-service.ts +102 -0
- package/src/azure/pricing/resolvers/disk.ts +170 -0
- package/src/azure/pricing/resolvers/index.ts +31 -0
- package/src/azure/pricing/resolvers/public-ip.ts +87 -0
- package/src/azure/pricing/resolvers/vm.ts +125 -0
- package/src/azure/pricing/schema.ts +69 -0
- package/src/azure/pricing/test-cache-dir.ts +26 -0
- package/src/azure/report.ts +86 -21
- package/src/azure/resources/__tests__/app-service.test.ts +165 -0
- package/src/azure/resources/app-service.ts +65 -4
- package/src/azure/resources/disk.ts +46 -4
- package/src/azure/resources/public-ip.ts +61 -2
- package/src/azure/resources/vm.ts +89 -11
- package/src/azure/types.ts +19 -3
- package/src/finding.ts +10 -3
- package/src/index.ts +4 -3
- package/src/schema.ts +7 -3
- package/src/types.ts +12 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the PricingService façade — covers memoization and error
|
|
3
|
+
* swallowing. The underlying resolvers are exercised in their own tests.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { DiskCache } from "../cache.js";
|
|
7
|
+
import { PricingClient } from "../client.js";
|
|
8
|
+
import { PricingService } from "../pricing-service.js";
|
|
9
|
+
import { makeTestCacheDir, removeTestCacheDir } from "../test-cache-dir.js";
|
|
10
|
+
function makeFetch(body) {
|
|
11
|
+
const spy = vi.fn().mockResolvedValue({
|
|
12
|
+
json: async () => body,
|
|
13
|
+
ok: true,
|
|
14
|
+
status: 200,
|
|
15
|
+
statusText: "OK",
|
|
16
|
+
});
|
|
17
|
+
return { fetch: spy, spy };
|
|
18
|
+
}
|
|
19
|
+
let dir;
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
dir = await makeTestCacheDir("pricing-service");
|
|
22
|
+
});
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await removeTestCacheDir(dir);
|
|
25
|
+
});
|
|
26
|
+
describe("PricingService", () => {
|
|
27
|
+
it("memoizes identical VM lookups within the same instance", async () => {
|
|
28
|
+
const body = {
|
|
29
|
+
Items: [
|
|
30
|
+
{
|
|
31
|
+
armRegionName: "westeurope",
|
|
32
|
+
armSkuName: "Standard_B2s",
|
|
33
|
+
currencyCode: "EUR",
|
|
34
|
+
productName: "Virtual Machines BS Series",
|
|
35
|
+
retailPrice: 0.05,
|
|
36
|
+
skuName: "B2s",
|
|
37
|
+
type: "Consumption",
|
|
38
|
+
unitOfMeasure: "1 Hour",
|
|
39
|
+
unitPrice: 0.05,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
NextPageLink: null,
|
|
43
|
+
};
|
|
44
|
+
const { fetch, spy } = makeFetch(body);
|
|
45
|
+
const client = new PricingClient({
|
|
46
|
+
cache: new DiskCache({ dir }),
|
|
47
|
+
fetch,
|
|
48
|
+
});
|
|
49
|
+
const service = new PricingService(client);
|
|
50
|
+
const [a, b] = await Promise.all([
|
|
51
|
+
service.resolveVm({
|
|
52
|
+
armRegionName: "westeurope",
|
|
53
|
+
armSkuName: "Standard_B2s",
|
|
54
|
+
}),
|
|
55
|
+
service.resolveVm({
|
|
56
|
+
armRegionName: "westeurope",
|
|
57
|
+
armSkuName: "Standard_B2s",
|
|
58
|
+
}),
|
|
59
|
+
]);
|
|
60
|
+
expect(a).toEqual(b);
|
|
61
|
+
// Only one underlying fetch despite two concurrent calls.
|
|
62
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
it("returns undefined and does not throw when the resolver errors", async () => {
|
|
65
|
+
const client = new PricingClient({
|
|
66
|
+
cache: new DiskCache({ dir }),
|
|
67
|
+
fetch: vi.fn().mockRejectedValue(new Error("boom")),
|
|
68
|
+
});
|
|
69
|
+
const service = new PricingService(client);
|
|
70
|
+
const result = await service.resolveVm({
|
|
71
|
+
armRegionName: "westeurope",
|
|
72
|
+
armSkuName: "Standard_B2s",
|
|
73
|
+
});
|
|
74
|
+
expect(result).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
it("scopes the in-memory memo by os hint", async () => {
|
|
77
|
+
const body = {
|
|
78
|
+
Items: [
|
|
79
|
+
{
|
|
80
|
+
armRegionName: "westeurope",
|
|
81
|
+
armSkuName: "Standard_B2s",
|
|
82
|
+
currencyCode: "EUR",
|
|
83
|
+
productName: "Virtual Machines BS Series",
|
|
84
|
+
retailPrice: 0.05,
|
|
85
|
+
skuName: "B2s",
|
|
86
|
+
type: "Consumption",
|
|
87
|
+
unitOfMeasure: "1 Hour",
|
|
88
|
+
unitPrice: 0.05,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
armRegionName: "westeurope",
|
|
92
|
+
armSkuName: "Standard_B2s",
|
|
93
|
+
currencyCode: "EUR",
|
|
94
|
+
productName: "Virtual Machines BS Series Windows",
|
|
95
|
+
retailPrice: 0.09,
|
|
96
|
+
skuName: "B2s",
|
|
97
|
+
type: "Consumption",
|
|
98
|
+
unitOfMeasure: "1 Hour",
|
|
99
|
+
unitPrice: 0.09,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
NextPageLink: null,
|
|
103
|
+
};
|
|
104
|
+
const { fetch, spy } = makeFetch(body);
|
|
105
|
+
const client = new PricingClient({
|
|
106
|
+
cache: new DiskCache({ dir }),
|
|
107
|
+
fetch,
|
|
108
|
+
});
|
|
109
|
+
const service = new PricingService(client);
|
|
110
|
+
const linux = await service.resolveVm({
|
|
111
|
+
armRegionName: "westeurope",
|
|
112
|
+
armSkuName: "Standard_B2s",
|
|
113
|
+
os: "linux",
|
|
114
|
+
});
|
|
115
|
+
const windows = await service.resolveVm({
|
|
116
|
+
armRegionName: "westeurope",
|
|
117
|
+
armSkuName: "Standard_B2s",
|
|
118
|
+
os: "windows",
|
|
119
|
+
});
|
|
120
|
+
expect(linux?.amount).toBeLessThan(windows?.amount ?? 0);
|
|
121
|
+
// Same underlying filter, served from DiskCache — one fetch only.
|
|
122
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("PricingService disk memoization", () => {
|
|
126
|
+
it("memoizes identical disk lookups within the same instance", async () => {
|
|
127
|
+
const body = {
|
|
128
|
+
Items: [
|
|
129
|
+
{
|
|
130
|
+
armRegionName: "westeurope",
|
|
131
|
+
currencyCode: "EUR",
|
|
132
|
+
productName: "Premium SSD Managed Disks",
|
|
133
|
+
retailPrice: 19.71,
|
|
134
|
+
skuName: "P10 LRS",
|
|
135
|
+
type: "Consumption",
|
|
136
|
+
unitOfMeasure: "1/Month",
|
|
137
|
+
unitPrice: 19.71,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
NextPageLink: null,
|
|
141
|
+
};
|
|
142
|
+
const { fetch, spy } = makeFetch(body);
|
|
143
|
+
const client = new PricingClient({
|
|
144
|
+
cache: new DiskCache({ dir }),
|
|
145
|
+
fetch,
|
|
146
|
+
});
|
|
147
|
+
const service = new PricingService(client);
|
|
148
|
+
const [a, b] = await Promise.all([
|
|
149
|
+
service.resolveDisk({
|
|
150
|
+
armRegionName: "westeurope",
|
|
151
|
+
diskSizeGiB: 128,
|
|
152
|
+
sku: "Premium_LRS",
|
|
153
|
+
}),
|
|
154
|
+
service.resolveDisk({
|
|
155
|
+
armRegionName: "westeurope",
|
|
156
|
+
diskSizeGiB: 128,
|
|
157
|
+
sku: "Premium_LRS",
|
|
158
|
+
}),
|
|
159
|
+
]);
|
|
160
|
+
expect(a).toEqual(b);
|
|
161
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
162
|
+
});
|
|
163
|
+
it("memoizes disk lookups with display and ARM region names as the same key", async () => {
|
|
164
|
+
const body = {
|
|
165
|
+
Items: [
|
|
166
|
+
{
|
|
167
|
+
armRegionName: "westeurope",
|
|
168
|
+
currencyCode: "EUR",
|
|
169
|
+
productName: "Premium SSD Managed Disks",
|
|
170
|
+
retailPrice: 19.71,
|
|
171
|
+
skuName: "P10 LRS",
|
|
172
|
+
type: "Consumption",
|
|
173
|
+
unitOfMeasure: "1/Month",
|
|
174
|
+
unitPrice: 19.71,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
NextPageLink: null,
|
|
178
|
+
};
|
|
179
|
+
const { fetch, spy } = makeFetch(body);
|
|
180
|
+
const client = new PricingClient({
|
|
181
|
+
cache: new DiskCache({ dir }),
|
|
182
|
+
fetch,
|
|
183
|
+
});
|
|
184
|
+
const service = new PricingService(client);
|
|
185
|
+
const [a, b] = await Promise.all([
|
|
186
|
+
service.resolveDisk({
|
|
187
|
+
armRegionName: "West Europe",
|
|
188
|
+
diskSizeGiB: 128,
|
|
189
|
+
sku: "Premium_LRS",
|
|
190
|
+
}),
|
|
191
|
+
service.resolveDisk({
|
|
192
|
+
armRegionName: "westeurope",
|
|
193
|
+
diskSizeGiB: 128,
|
|
194
|
+
sku: "Premium_LRS",
|
|
195
|
+
}),
|
|
196
|
+
]);
|
|
197
|
+
expect(a).toEqual(b);
|
|
198
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe("PricingService App Service Plan memoization", () => {
|
|
202
|
+
it("memoizes identical App Service Plan lookups within the same instance", async () => {
|
|
203
|
+
const body = {
|
|
204
|
+
Items: [
|
|
205
|
+
{
|
|
206
|
+
armRegionName: "westeurope",
|
|
207
|
+
currencyCode: "EUR",
|
|
208
|
+
meterName: "S1 App",
|
|
209
|
+
productName: "Azure App Service Standard Plan - Linux",
|
|
210
|
+
retailPrice: 0.08337,
|
|
211
|
+
skuName: "S1",
|
|
212
|
+
type: "Consumption",
|
|
213
|
+
unitOfMeasure: "1 Hour",
|
|
214
|
+
unitPrice: 0.08337,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
NextPageLink: null,
|
|
218
|
+
};
|
|
219
|
+
const { fetch, spy } = makeFetch(body);
|
|
220
|
+
const client = new PricingClient({
|
|
221
|
+
cache: new DiskCache({ dir }),
|
|
222
|
+
fetch,
|
|
223
|
+
});
|
|
224
|
+
const service = new PricingService(client);
|
|
225
|
+
const [a, b] = await Promise.all([
|
|
226
|
+
service.resolveAppServicePlan({
|
|
227
|
+
armRegionName: "westeurope",
|
|
228
|
+
os: "linux",
|
|
229
|
+
skuName: "S1",
|
|
230
|
+
workerCount: 1,
|
|
231
|
+
}),
|
|
232
|
+
service.resolveAppServicePlan({
|
|
233
|
+
armRegionName: "westeurope",
|
|
234
|
+
os: "linux",
|
|
235
|
+
skuName: "S1",
|
|
236
|
+
workerCount: 1,
|
|
237
|
+
}),
|
|
238
|
+
]);
|
|
239
|
+
expect(a).toEqual(b);
|
|
240
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
241
|
+
});
|
|
242
|
+
it("memoizes App Service Plan lookups by canonical region and worker count", async () => {
|
|
243
|
+
const body = {
|
|
244
|
+
Items: [
|
|
245
|
+
{
|
|
246
|
+
armRegionName: "westeurope",
|
|
247
|
+
currencyCode: "EUR",
|
|
248
|
+
meterName: "S1 App",
|
|
249
|
+
productName: "Azure App Service Standard Plan - Linux",
|
|
250
|
+
retailPrice: 0.08337,
|
|
251
|
+
skuName: "S1",
|
|
252
|
+
type: "Consumption",
|
|
253
|
+
unitOfMeasure: "1 Hour",
|
|
254
|
+
unitPrice: 0.08337,
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
NextPageLink: null,
|
|
258
|
+
};
|
|
259
|
+
const { fetch, spy } = makeFetch(body);
|
|
260
|
+
const client = new PricingClient({
|
|
261
|
+
cache: new DiskCache({ dir }),
|
|
262
|
+
fetch,
|
|
263
|
+
});
|
|
264
|
+
const service = new PricingService(client);
|
|
265
|
+
const [a, b] = await Promise.all([
|
|
266
|
+
service.resolveAppServicePlan({
|
|
267
|
+
armRegionName: "West Europe",
|
|
268
|
+
os: "linux",
|
|
269
|
+
skuName: "S1",
|
|
270
|
+
workerCount: 1.2,
|
|
271
|
+
}),
|
|
272
|
+
service.resolveAppServicePlan({
|
|
273
|
+
armRegionName: "westeurope",
|
|
274
|
+
os: "linux",
|
|
275
|
+
skuName: "S1",
|
|
276
|
+
workerCount: 2,
|
|
277
|
+
}),
|
|
278
|
+
]);
|
|
279
|
+
expect(a).toEqual(b);
|
|
280
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
//# sourceMappingURL=pricing-service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing-service.test.js","sourceRoot":"","sources":["../../../../src/azure/pricing/__tests__/pricing-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk-backed cache for Azure Retail Prices API responses.
|
|
3
|
+
*
|
|
4
|
+
* Retail prices change rarely (Azure publishes a new snapshot at most once
|
|
5
|
+
* a month) so a long TTL is safe and dramatically cuts cold-start latency
|
|
6
|
+
* for `dx savemoney`. Each cache entry is a JSON file whose name is the
|
|
7
|
+
* SHA-256 of the request key (the OData `$filter` string); the entry is
|
|
8
|
+
* considered fresh while `now - mtime <= ttl`.
|
|
9
|
+
*
|
|
10
|
+
* The cache directory is created lazily on first write. Read/write
|
|
11
|
+
* failures never throw: a corrupted or unreadable entry simply behaves
|
|
12
|
+
* like a miss so the caller refetches from the API. This matches the
|
|
13
|
+
* "best-effort cache" semantics expected by HTTP-level caches.
|
|
14
|
+
*/
|
|
15
|
+
/** Defaults documented in the package README. */
|
|
16
|
+
export declare const DEFAULT_CACHE_DIR: string;
|
|
17
|
+
export declare const DEFAULT_CACHE_TTL_MS: number;
|
|
18
|
+
export type DiskCacheOptions = {
|
|
19
|
+
/** Absolute path where cache entries are stored. */
|
|
20
|
+
dir?: string;
|
|
21
|
+
/** Maximum age of an entry before it is treated as a miss. */
|
|
22
|
+
ttlMs?: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Best-effort JSON cache. Keys are arbitrary strings; values are anything
|
|
26
|
+
* serialisable via `JSON.stringify`.
|
|
27
|
+
*/
|
|
28
|
+
export declare class DiskCache {
|
|
29
|
+
readonly dir: string;
|
|
30
|
+
readonly ttlMs: number;
|
|
31
|
+
constructor(options?: DiskCacheOptions);
|
|
32
|
+
/**
|
|
33
|
+
* Returns the cached value for `key` if present and still fresh.
|
|
34
|
+
* Returns `undefined` on miss, expiry, or any I/O / parse error.
|
|
35
|
+
*/
|
|
36
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
37
|
+
/** Absolute filesystem path used to store `key`. Exposed for tests. */
|
|
38
|
+
pathFor(key: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Persists `value` under `key`. Silently swallows write errors so a
|
|
41
|
+
* read-only filesystem cannot break the calling pipeline.
|
|
42
|
+
*/
|
|
43
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/azure/pricing/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,QAI7B,CAAC;AACF,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,SAAS;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,OAAO,GAAE,gBAAqB;IAK1C;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAiBjD,uEAAuE;IACvE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAK5B;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAYnD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk-backed cache for Azure Retail Prices API responses.
|
|
3
|
+
*
|
|
4
|
+
* Retail prices change rarely (Azure publishes a new snapshot at most once
|
|
5
|
+
* a month) so a long TTL is safe and dramatically cuts cold-start latency
|
|
6
|
+
* for `dx savemoney`. Each cache entry is a JSON file whose name is the
|
|
7
|
+
* SHA-256 of the request key (the OData `$filter` string); the entry is
|
|
8
|
+
* considered fresh while `now - mtime <= ttl`.
|
|
9
|
+
*
|
|
10
|
+
* The cache directory is created lazily on first write. Read/write
|
|
11
|
+
* failures never throw: a corrupted or unreadable entry simply behaves
|
|
12
|
+
* like a miss so the caller refetches from the API. This matches the
|
|
13
|
+
* "best-effort cache" semantics expected by HTTP-level caches.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { mkdir, open, writeFile } from "node:fs/promises";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
/** Defaults documented in the package README. */
|
|
20
|
+
export const DEFAULT_CACHE_DIR = join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "dx-savemoney", "pricing");
|
|
21
|
+
export const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Best-effort JSON cache. Keys are arbitrary strings; values are anything
|
|
24
|
+
* serialisable via `JSON.stringify`.
|
|
25
|
+
*/
|
|
26
|
+
export class DiskCache {
|
|
27
|
+
dir;
|
|
28
|
+
ttlMs;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.dir = options.dir ?? DEFAULT_CACHE_DIR;
|
|
31
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the cached value for `key` if present and still fresh.
|
|
35
|
+
* Returns `undefined` on miss, expiry, or any I/O / parse error.
|
|
36
|
+
*/
|
|
37
|
+
async get(key) {
|
|
38
|
+
const path = this.pathFor(key);
|
|
39
|
+
try {
|
|
40
|
+
const file = await open(path, "r");
|
|
41
|
+
try {
|
|
42
|
+
const stats = await file.stat();
|
|
43
|
+
if (Date.now() - stats.mtimeMs > this.ttlMs)
|
|
44
|
+
return undefined;
|
|
45
|
+
const raw = await file.readFile("utf8");
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await file.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Absolute filesystem path used to store `key`. Exposed for tests. */
|
|
57
|
+
pathFor(key) {
|
|
58
|
+
const hash = createHash("sha256").update(key).digest("hex");
|
|
59
|
+
return join(this.dir, `${hash}.json`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Persists `value` under `key`. Silently swallows write errors so a
|
|
63
|
+
* read-only filesystem cannot break the calling pipeline.
|
|
64
|
+
*/
|
|
65
|
+
async set(key, value) {
|
|
66
|
+
const path = this.pathFor(key);
|
|
67
|
+
try {
|
|
68
|
+
await mkdir(this.dir, { mode: 0o700, recursive: true });
|
|
69
|
+
await writeFile(path, JSON.stringify(value), {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
mode: 0o600,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Best-effort: cache failures must not surface to callers.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/azure/pricing/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,iDAAiD;AACjD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CACnC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,EACvD,cAAc,EACd,SAAS,CACV,CAAC;AACF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AASxD;;;GAGG;AACH,MAAM,OAAO,SAAS;IACX,GAAG,CAAS;IACZ,KAAK,CAAS;IAEvB,YAAY,UAA4B,EAAE;QACxC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,iBAAiB,CAAC;QAC5C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,oBAAoB,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBAChC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK;oBAAE,OAAO,SAAS,CAAC;gBAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBACxC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;YAC9B,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,OAAO,CAAC,GAAW;QACjB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IACxC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,KAAQ;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;gBAC3C,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,KAAK;aACZ,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;QAC7D,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the Azure Retail Prices REST API.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: https://prices.azure.com/api/retail/prices
|
|
5
|
+
*
|
|
6
|
+
* The API is **public and unauthenticated** — no `TokenCredential`
|
|
7
|
+
* required — but it is rate-limited per source IP. To stay polite we:
|
|
8
|
+
* 1. Cache the full paginated result for each `$filter` on disk
|
|
9
|
+
* (`DiskCache`, 24h TTL by default) and return cached items
|
|
10
|
+
* immediately on hit.
|
|
11
|
+
* 2. Follow `NextPageLink` sequentially (no parallel page fetches).
|
|
12
|
+
*
|
|
13
|
+
* The client is constructed with an optional `fetch` implementation to
|
|
14
|
+
* make it trivially mockable in tests; in production it uses the global
|
|
15
|
+
* `fetch` available since Node 18.
|
|
16
|
+
*/
|
|
17
|
+
import type { PriceItem } from "./schema.js";
|
|
18
|
+
import { DiskCache } from "./cache.js";
|
|
19
|
+
export type FetchLike = (input: string, init?: {
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}) => Promise<{
|
|
22
|
+
json(): Promise<unknown>;
|
|
23
|
+
ok: boolean;
|
|
24
|
+
status: number;
|
|
25
|
+
statusText: string;
|
|
26
|
+
}>;
|
|
27
|
+
export type PricingClientOptions = {
|
|
28
|
+
/** Override the base URL. Useful for staging or fixtures. */
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
/** Disk cache instance. Defaults to the user's cache directory. */
|
|
31
|
+
cache?: DiskCache;
|
|
32
|
+
/**
|
|
33
|
+
* Currency to ask the API for. The response items always carry their
|
|
34
|
+
* own `currencyCode`, but specifying it here returns localised prices
|
|
35
|
+
* where available. Defaults to "EUR".
|
|
36
|
+
*/
|
|
37
|
+
currencyCode?: string;
|
|
38
|
+
/** Inject a custom `fetch` (used by tests). Defaults to the global one. */
|
|
39
|
+
fetch?: FetchLike;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Thin wrapper around the Retail Prices endpoint.
|
|
43
|
+
*
|
|
44
|
+
* Use `query(filter)` with an OData `$filter` expression, e.g.:
|
|
45
|
+
* serviceName eq 'Virtual Machines' and armRegionName eq 'westeurope' and armSkuName eq 'Standard_B2s'
|
|
46
|
+
*/
|
|
47
|
+
export declare class PricingClient {
|
|
48
|
+
private readonly baseUrl;
|
|
49
|
+
private readonly cache;
|
|
50
|
+
private readonly currencyCode;
|
|
51
|
+
private readonly fetchImpl;
|
|
52
|
+
private readonly logger;
|
|
53
|
+
constructor(options?: PricingClientOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Returns every meter that matches the given OData `$filter`, following
|
|
56
|
+
* pagination transparently. Results are cached for `cache.ttlMs`.
|
|
57
|
+
*
|
|
58
|
+
* Throws when the API returns a non-2xx status or a response that fails
|
|
59
|
+
* schema validation: pricing lookups are advisory (callers should treat
|
|
60
|
+
* "no data" as "skip the savings estimate") but a malformed payload
|
|
61
|
+
* signals a real upstream change that deserves attention.
|
|
62
|
+
*/
|
|
63
|
+
query(filter: string): Promise<PriceItem[]>;
|
|
64
|
+
private buildUrl;
|
|
65
|
+
private fetchPage;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/azure/pricing/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOvC,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC5B,OAAO,CAAC;IACX,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG;IACjC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgD;gBAE3D,OAAO,GAAE,oBAAyB;IAW9C;;;;;;;;OAQG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BjD,OAAO,CAAC,QAAQ;YASF,SAAS;CAcxB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the Azure Retail Prices REST API.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: https://prices.azure.com/api/retail/prices
|
|
5
|
+
*
|
|
6
|
+
* The API is **public and unauthenticated** — no `TokenCredential`
|
|
7
|
+
* required — but it is rate-limited per source IP. To stay polite we:
|
|
8
|
+
* 1. Cache the full paginated result for each `$filter` on disk
|
|
9
|
+
* (`DiskCache`, 24h TTL by default) and return cached items
|
|
10
|
+
* immediately on hit.
|
|
11
|
+
* 2. Follow `NextPageLink` sequentially (no parallel page fetches).
|
|
12
|
+
*
|
|
13
|
+
* The client is constructed with an optional `fetch` implementation to
|
|
14
|
+
* make it trivially mockable in tests; in production it uses the global
|
|
15
|
+
* `fetch` available since Node 18.
|
|
16
|
+
*/
|
|
17
|
+
import { getLogger } from "@logtape/logtape";
|
|
18
|
+
import { DiskCache } from "./cache.js";
|
|
19
|
+
import { PricingResponseSchema } from "./schema.js";
|
|
20
|
+
const DEFAULT_BASE_URL = "https://prices.azure.com/api/retail/prices";
|
|
21
|
+
/** Hard cap on pages to follow — protects against runaway loops. */
|
|
22
|
+
const MAX_PAGES = 100;
|
|
23
|
+
/**
|
|
24
|
+
* Thin wrapper around the Retail Prices endpoint.
|
|
25
|
+
*
|
|
26
|
+
* Use `query(filter)` with an OData `$filter` expression, e.g.:
|
|
27
|
+
* serviceName eq 'Virtual Machines' and armRegionName eq 'westeurope' and armSkuName eq 'Standard_B2s'
|
|
28
|
+
*/
|
|
29
|
+
export class PricingClient {
|
|
30
|
+
baseUrl;
|
|
31
|
+
cache;
|
|
32
|
+
currencyCode;
|
|
33
|
+
fetchImpl;
|
|
34
|
+
logger = getLogger(["savemoney", "azure", "pricing"]);
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
37
|
+
this.cache = options.cache ?? new DiskCache();
|
|
38
|
+
this.currencyCode = options.currencyCode ?? "EUR";
|
|
39
|
+
// The global `fetch` exists in Node ≥ 18 / 24 but TypeScript types it
|
|
40
|
+
// loosely; we narrow to `FetchLike` to keep the surface intentional.
|
|
41
|
+
this.fetchImpl =
|
|
42
|
+
options.fetch ??
|
|
43
|
+
((input, init) => fetch(input, init));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns every meter that matches the given OData `$filter`, following
|
|
47
|
+
* pagination transparently. Results are cached for `cache.ttlMs`.
|
|
48
|
+
*
|
|
49
|
+
* Throws when the API returns a non-2xx status or a response that fails
|
|
50
|
+
* schema validation: pricing lookups are advisory (callers should treat
|
|
51
|
+
* "no data" as "skip the savings estimate") but a malformed payload
|
|
52
|
+
* signals a real upstream change that deserves attention.
|
|
53
|
+
*/
|
|
54
|
+
async query(filter) {
|
|
55
|
+
const cacheKey = `${this.currencyCode}|${filter}`;
|
|
56
|
+
const cached = await this.cache.get(cacheKey);
|
|
57
|
+
if (cached) {
|
|
58
|
+
this.logger.debug(`pricing cache hit (${cached.length} items): ${filter}`);
|
|
59
|
+
return cached;
|
|
60
|
+
}
|
|
61
|
+
const items = [];
|
|
62
|
+
const firstUrl = this.buildUrl(filter);
|
|
63
|
+
let nextUrl = firstUrl;
|
|
64
|
+
for (let page = 0; nextUrl && page < MAX_PAGES; page++) {
|
|
65
|
+
const response = await this.fetchPage(nextUrl);
|
|
66
|
+
items.push(...response.Items);
|
|
67
|
+
nextUrl = response.NextPageLink ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
if (nextUrl) {
|
|
70
|
+
this.logger.warn(`pricing query truncated at ${MAX_PAGES} pages: ${filter}`);
|
|
71
|
+
}
|
|
72
|
+
await this.cache.set(cacheKey, items);
|
|
73
|
+
return items;
|
|
74
|
+
}
|
|
75
|
+
buildUrl(filter) {
|
|
76
|
+
const params = new URLSearchParams({
|
|
77
|
+
$filter: filter,
|
|
78
|
+
"api-version": "2023-01-01-preview",
|
|
79
|
+
currencyCode: this.currencyCode,
|
|
80
|
+
});
|
|
81
|
+
return `${this.baseUrl}?${params.toString()}`;
|
|
82
|
+
}
|
|
83
|
+
async fetchPage(url) {
|
|
84
|
+
const res = await this.fetchImpl(url);
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
throw new Error(`Azure Retail Prices API returned ${res.status} ${res.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
const body = await res.json().catch((error) => {
|
|
89
|
+
throw new Error("Azure Retail Prices API returned invalid JSON", {
|
|
90
|
+
cause: error,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return PricingResponseSchema.parse(body);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/azure/pricing/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/pricing/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/pricing/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAA6B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|