@blob-solutions/vcr-am-sdk 0.8.0 → 0.10.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/CHANGELOG.md +37 -0
- package/LICENSE +15 -0
- package/README.md +226 -21
- package/lib/index.cjs +471 -1
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +453 -30
- package/lib/index.d.ts +453 -30
- package/lib/index.global.js +64 -12
- package/lib/index.global.js.map +1 -1
- package/lib/index.js +446 -1
- package/lib/index.js.map +1 -1
- package/package.json +60 -26
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@blob-solutions/vcr-am-sdk`. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.10.0] — 2026-05-02
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- `SaleAmount.prePayment` was renamed to `prepayment`. The 0.8.0 spelling was a silent bug — the server only accepted `prepayment`, so any consumer using `prePayment` was sending an unrecognized field that the server ignored. If your code referenced `prePayment`, update it.
|
|
10
|
+
- The `searchClassifierCategory` method now hits `/searchByClassifier` (the actual API path) instead of `/searchClassifier`. The 0.8.0 path returned 404 against production, so any working consumer can't have been using it.
|
|
11
|
+
- The `simple-typed-fetch` runtime dependency is removed. Errors thrown by the SDK now extend the new `VCRError` hierarchy (`VCRApiError`, `VCRValidationError`, `VCRNetworkError`) instead of whatever shape `simple-typed-fetch` produced. Update `catch` blocks accordingly.
|
|
12
|
+
- `./browser` subpath export was removed from `package.json`. The IIFE bundle is still shipped at `lib/index.global.js` and remains usable via `<script src="https://unpkg.com/@blob-solutions/vcr-am-sdk/lib/index.global.js">`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Full coverage of the public `/api/v1` surface: `getSale`, `registerSaleRefund`, `registerPrepayment`, `getPrepayment`, `registerPrepaymentRefund`, `listCashiers`, `createCashier`, `createOffer`, `createDepartment`. Existing `registerSale` and `searchClassifierCategory` retained.
|
|
17
|
+
- Structured error classes: `VCRError` (abstract base), `VCRApiError`, `VCRValidationError`, `VCRNetworkError`. All carry `url`, `status` (where applicable), and the original Zod issues / cause.
|
|
18
|
+
- Configurable client: `VCRClientOptions` exposes `baseUrl`, `fetch`, and `timeoutMs`. Per-request `RequestOptions` adds `signal` and `timeoutMs`.
|
|
19
|
+
- Schema-validated responses for every endpoint (Zod 4). Response shape changes will fail loudly with `VCRValidationError` instead of silently returning broken data.
|
|
20
|
+
- 44-test Vitest suite, 100% line/function coverage, `publint` and `attw` checks in `prepublishOnly`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Native `fetch` and `AbortSignal` used directly — no polyfill, no wrapper library.
|
|
25
|
+
- The whole request flow (connect → body read → schema parse) is bounded by a single timer; previously the timer was cleared as soon as headers arrived, leaving body reads unbounded.
|
|
26
|
+
- `Zod` upgraded `^3.24.1` → `^4.4.1`.
|
|
27
|
+
- `TypeScript` upgraded `^5.7.2` → `^5.9.3`.
|
|
28
|
+
- Minimum Node version raised to `>=22.12.0`.
|
|
29
|
+
- `package.json` exports map split for proper dual ESM/CJS types resolution (`.d.ts` for `import`, `.d.cts` for `require`); validated by `publint` and `attw`.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `simple-typed-fetch` and `type-fest` runtime dependencies.
|
|
34
|
+
|
|
35
|
+
## [0.8.0] and earlier
|
|
36
|
+
|
|
37
|
+
Not retroactively documented. See git history.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024–2026 Alex Kraiz
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,37 +1,242 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@blob-solutions/vcr-am-sdk`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@blob-solutions/vcr-am-sdk)
|
|
4
|
+
[](https://www.npmjs.com/package/@blob-solutions/vcr-am-sdk)
|
|
5
|
+
[](https://github.com/blob-am/vcr-am-sdk/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Official TypeScript / JavaScript SDK for the [VCR.AM](https://vcr.am) Virtual Cash Register API — register sales, refunds, and prepayments through Armenia's State Revenue Committee (SRC).
|
|
9
|
+
|
|
10
|
+
- **One runtime dep** — just [Zod](https://zod.dev). Native `fetch`, `AbortSignal`, no polyfills.
|
|
11
|
+
- **Cross-runtime** — Node 22+, Bun, Deno, modern browsers. ESM + CJS + IIFE.
|
|
12
|
+
- **Schema-validated** — every response is parsed with Zod 4; contract drift fails loudly with structured errors.
|
|
13
|
+
- **Strict types** — branded errors, `RequireAtLeastOne` amount unions, discriminated buyer / offer types.
|
|
14
|
+
- **Verified** — `publint` clean, `attw` 🟢 across all four resolution modes, 100% line coverage.
|
|
15
|
+
|
|
16
|
+
## Table of contents
|
|
17
|
+
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick start](#quick-start)
|
|
20
|
+
- [Configuration](#configuration)
|
|
21
|
+
- [API reference](#api-reference)
|
|
22
|
+
- [Sales](#sales)
|
|
23
|
+
- [Prepayments](#prepayments)
|
|
24
|
+
- [Cashiers](#cashiers)
|
|
25
|
+
- [Offers and departments](#offers-and-departments)
|
|
26
|
+
- [Classifier search](#classifier-search)
|
|
27
|
+
- [Error handling](#error-handling)
|
|
28
|
+
- [Cancellation and timeouts](#cancellation-and-timeouts)
|
|
29
|
+
- [Idempotency and retries](#idempotency-and-retries)
|
|
30
|
+
- [Browser usage](#browser-usage)
|
|
31
|
+
- [Development](#development)
|
|
32
|
+
- [Versioning](#versioning)
|
|
4
33
|
|
|
5
34
|
## Installation
|
|
6
35
|
|
|
7
36
|
```bash
|
|
8
|
-
|
|
37
|
+
pnpm add @blob-solutions/vcr-am-sdk
|
|
38
|
+
# or: npm install / yarn add / bun add
|
|
9
39
|
```
|
|
10
40
|
|
|
11
|
-
|
|
41
|
+
You'll need an API key from your VCR.AM dashboard.
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
12
44
|
|
|
13
45
|
```typescript
|
|
14
|
-
import { VCRClient
|
|
46
|
+
import { VCRClient } from "@blob-solutions/vcr-am-sdk";
|
|
15
47
|
|
|
16
|
-
const
|
|
17
|
-
|
|
48
|
+
const apiKey = process.env.VCR_AM_API_KEY;
|
|
49
|
+
if (apiKey === undefined) throw new Error("VCR_AM_API_KEY is required");
|
|
18
50
|
|
|
19
|
-
const
|
|
51
|
+
const vcr = new VCRClient(apiKey);
|
|
20
52
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
53
|
+
const { crn, srcReceiptId, fiscal } = await vcr.registerSale({
|
|
54
|
+
cashier: { id: 1 },
|
|
55
|
+
items: [
|
|
56
|
+
{
|
|
57
|
+
offer: { externalId: "sku-coffee" },
|
|
58
|
+
department: { id: 1 },
|
|
59
|
+
quantity: "1",
|
|
60
|
+
price: "1500",
|
|
61
|
+
unit: "pc",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
amount: { cash: "1500" },
|
|
65
|
+
buyer: { type: "individual" },
|
|
66
|
+
});
|
|
24
67
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
console.log({ crn, srcReceiptId, fiscal });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const vcr = new VCRClient(apiKey, {
|
|
75
|
+
baseUrl: "https://vcr.am/api/v1", // override for staging or self-hosted
|
|
76
|
+
fetch: customFetch, // injectable fetch (e.g. undici, MSW)
|
|
77
|
+
timeoutMs: 30_000, // default 30s; pass `null` to disable
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Per-request overrides apply to every method's optional second argument:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
|
|
86
|
+
await vcr.registerSale(sale, {
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
timeoutMs: 5_000,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API reference
|
|
93
|
+
|
|
94
|
+
Every method returns a typed, schema-validated response and accepts an optional `RequestOptions` (`{ signal?, timeoutMs? }`) as its last argument.
|
|
95
|
+
|
|
96
|
+
### Sales
|
|
97
|
+
|
|
98
|
+
| Method | Endpoint | Returns |
|
|
99
|
+
| --------------------------- | ------------------- | ----------------------------- |
|
|
100
|
+
| `registerSale(input)` | `POST /sales` | `RegisterSaleResponse` |
|
|
101
|
+
| `getSale(saleId)` | `GET /sales/{id}` | `SaleDetail` |
|
|
102
|
+
| `registerSaleRefund(input)` | `POST /sales/refund`| `RegisterSaleRefundResponse` |
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const refund = await vcr.registerSaleRefund({
|
|
106
|
+
cashier: { id: 1 },
|
|
107
|
+
saleId: 42,
|
|
108
|
+
reason: "customer_request",
|
|
109
|
+
refundAmounts: { cash: "1500" },
|
|
110
|
+
items: [{ srcId: 17, quantity: "1" }],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Prepayments
|
|
115
|
+
|
|
116
|
+
| Method | Endpoint | Returns |
|
|
117
|
+
| --------------------------------- | ------------------------- | ------------------------------------ |
|
|
118
|
+
| `registerPrepayment(input)` | `POST /prepayments` | `RegisterPrepaymentResponse` |
|
|
119
|
+
| `getPrepayment(id)` | `GET /prepayments/{id}` | `PrepaymentDetail` |
|
|
120
|
+
| `registerPrepaymentRefund(input)` | `POST /prepayments/refund`| `RegisterPrepaymentRefundResponse` |
|
|
121
|
+
|
|
122
|
+
### Cashiers
|
|
123
|
+
|
|
124
|
+
| Method | Endpoint | Returns |
|
|
125
|
+
| ----------------------- | ----------------- | ------------------------ |
|
|
126
|
+
| `listCashiers()` | `GET /cashiers` | `CashierListItem[]` |
|
|
127
|
+
| `createCashier(input)` | `POST /cashiers` | `CreateCashierResponse` |
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const cashier = await vcr.createCashier({
|
|
131
|
+
name: {
|
|
132
|
+
value: { hy: "Արամ", en: "Aram" },
|
|
133
|
+
localizationStrategy: "translation",
|
|
35
134
|
},
|
|
135
|
+
password: "1234",
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Offers and departments
|
|
140
|
+
|
|
141
|
+
| Method | Endpoint | Returns |
|
|
142
|
+
| ------------------------- | -------------------- | --------------------------- |
|
|
143
|
+
| `createOffer(input)` | `POST /offers` | `CreateOfferResponse` |
|
|
144
|
+
| `createDepartment(input)` | `POST /departments` | `CreateDepartmentResponse` |
|
|
145
|
+
|
|
146
|
+
### Classifier search
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const matches = await vcr.searchClassifier({
|
|
150
|
+
query: "coffee",
|
|
151
|
+
type: "product",
|
|
152
|
+
language: "en",
|
|
36
153
|
});
|
|
37
154
|
```
|
|
155
|
+
|
|
156
|
+
## Error handling
|
|
157
|
+
|
|
158
|
+
All SDK errors derive from `VCRError`. Catch the base class for a single handler, or narrow on the specific subclass.
|
|
159
|
+
|
|
160
|
+
| Class | Thrown when |
|
|
161
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| `VCRApiError` | Server returned a non-2xx with the unified `{ error, issues? }` envelope. Carries `status`, `body`, `url`. |
|
|
163
|
+
| `VCRValidationError` | Response body did not match the expected schema (2xx or non-envelope error). Carries `issues` (Zod) and `raw`. |
|
|
164
|
+
| `VCRNetworkError` | Network-level failure: DNS, connection refused, abort, timeout. Wraps the underlying error in `cause`. |
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import {
|
|
168
|
+
VCRApiError,
|
|
169
|
+
VCRError,
|
|
170
|
+
VCRNetworkError,
|
|
171
|
+
VCRValidationError,
|
|
172
|
+
} from "@blob-solutions/vcr-am-sdk";
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await vcr.registerSale(sale);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof VCRApiError) {
|
|
178
|
+
console.error(error.status, error.body.error, error.body.issues);
|
|
179
|
+
} else if (error instanceof VCRValidationError) {
|
|
180
|
+
console.error("unexpected response shape", error.issues, error.raw);
|
|
181
|
+
} else if (error instanceof VCRNetworkError) {
|
|
182
|
+
console.error("network failure", error.cause);
|
|
183
|
+
} else if (error instanceof VCRError) {
|
|
184
|
+
console.error("VCR error", error);
|
|
185
|
+
} else {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Cancellation and timeouts
|
|
192
|
+
|
|
193
|
+
The SDK respects the standard `AbortSignal`:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
setTimeout(() => controller.abort(), 5_000);
|
|
198
|
+
|
|
199
|
+
await vcr.getSale(42, { signal: controller.signal });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The whole request flow — connection, body read, schema parse — is bounded by a single timer. Default timeout is 30 seconds; configurable per-client (`timeoutMs`) and per-request (`{ timeoutMs }`). Pass `null` to disable.
|
|
203
|
+
|
|
204
|
+
## Idempotency and retries
|
|
205
|
+
|
|
206
|
+
**The SDK does not retry.** This is intentional. The fiscal API does not currently support idempotency keys, so retrying a `registerSale` after a network blip can double-fiscalize a receipt. Implement application-level retry only on operations you have confirmed are safe to repeat (typically reads: `getSale`, `getPrepayment`, `listCashiers`, `searchClassifier`).
|
|
207
|
+
|
|
208
|
+
## Browser usage
|
|
209
|
+
|
|
210
|
+
The IIFE bundle ships at `lib/index.global.js` and exposes a global `vcrsdk`:
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<script src="https://unpkg.com/@blob-solutions/vcr-am-sdk/lib/index.global.js"></script>
|
|
214
|
+
<script>
|
|
215
|
+
const vcr = new vcrsdk.VCRClient("YOUR_KEY");
|
|
216
|
+
</script>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
For ESM bundlers (Vite, esbuild, Webpack 5+), the package's `"exports"` map will pick the right entry automatically — no special config needed.
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pnpm install
|
|
225
|
+
pnpm check # lint + typecheck + tests
|
|
226
|
+
pnpm test # vitest run
|
|
227
|
+
pnpm test:watch
|
|
228
|
+
pnpm test:coverage
|
|
229
|
+
pnpm typecheck
|
|
230
|
+
pnpm lint # biome + custom no-`as`-cast check
|
|
231
|
+
pnpm lint:fix
|
|
232
|
+
pnpm build # ESM + CJS + IIFE + .d.ts via tsup
|
|
233
|
+
pnpm lint:package # publint + are-the-types-wrong
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Versioning
|
|
237
|
+
|
|
238
|
+
[Semantic Versioning](https://semver.org). See [CHANGELOG.md](CHANGELOG.md) for breaking-change notes; in particular, **0.10.0 fixes two silent bugs** in 0.8.0 (`SaleAmount.prePayment` → `prepayment`, and `searchClassifierCategory` now hits the correct endpoint).
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
ISC © Alex Kraiz
|