@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 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
- # VCR.AM SDK
1
+ # `@blob-solutions/vcr-am-sdk`
2
2
 
3
- This is the official VCR.AM SDK for TS/JS. It provides a simple way to interact with the VCR.AM API.
3
+ [![npm version](https://img.shields.io/npm/v/@blob-solutions/vcr-am-sdk?color=cb3837&logo=npm)](https://www.npmjs.com/package/@blob-solutions/vcr-am-sdk)
4
+ [![types](https://img.shields.io/npm/types/@blob-solutions/vcr-am-sdk?logo=typescript&logoColor=white)](https://www.npmjs.com/package/@blob-solutions/vcr-am-sdk)
5
+ [![CI](https://github.com/blob-am/vcr-am-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/blob-am/vcr-am-sdk/actions/workflows/ci.yml)
6
+ [![License: ISC](https://img.shields.io/badge/license-ISC-blue.svg)](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
- npm install @blob-solutions/vcr-am-sdk
37
+ pnpm add @blob-solutions/vcr-am-sdk
38
+ # or: npm install / yarn add / bun add
9
39
  ```
10
40
 
11
- ## Usage
41
+ You'll need an API key from your VCR.AM dashboard.
42
+
43
+ ## Quick start
12
44
 
13
45
  ```typescript
14
- import { VCRClient, SaleItem } from "@blob-solutions/vcr-am-sdk";
46
+ import { VCRClient } from "@blob-solutions/vcr-am-sdk";
15
47
 
16
- const VCR_AM_API_KEY = "YOUR_API_KEY";
17
- const VCR_AM_CASHIER_ID = "YOUR_CASHIER_ID";
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 vcrClient = new VCRClient(VCR_AM_API_KEY);
51
+ const vcr = new VCRClient(apiKey);
20
52
 
21
- const items: SaleItems = [
22
- // Add your items here
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
- const { crn, srcReceiptId } = await vcrClient.registerSale({
26
- items,
27
- amount: {
28
- // Add your amount here
29
- },
30
- buyer: {
31
- type: "individual",
32
- },
33
- cashier: {
34
- id: apiEnv.VCR_AM_CASHIER_ID,
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