artefactby-ozon-seller-api 0.1.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/LICENSE +21 -0
- package/README.en.md +196 -0
- package/README.md +196 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +69980 -0
- package/dist/index.js +326 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +295 -0
- package/dist/index.mjs.map +1 -0
- package/dist/limiter/index.d.mts +1 -0
- package/dist/limiter/index.d.ts +119 -0
- package/dist/limiter/index.js +276 -0
- package/dist/limiter/index.js.map +1 -0
- package/dist/limiter/index.mjs +244 -0
- package/dist/limiter/index.mjs.map +1 -0
- package/dist/rate-limit-CNNNIfTX.d.ts +39 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 artefactby
|
|
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.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# artefactby-ozon-seller-api
|
|
2
|
+
|
|
3
|
+
Unofficial typed [Ozon Seller API](https://docs.ozon.ru/api/seller/) client for
|
|
4
|
+
TypeScript and JavaScript.
|
|
5
|
+
|
|
6
|
+
[README на русском](./README.md)
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { OzonClient } from 'artefactby-ozon-seller-api';
|
|
10
|
+
|
|
11
|
+
const client = new OzonClient({
|
|
12
|
+
clientId: process.env.OZON_CLIENT_ID!,
|
|
13
|
+
apiKey: process.env.OZON_API_KEY!,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// `items` is typed from the path literal — no generics, no casts
|
|
17
|
+
const { items } = await client.request('/v5/product/info/prices', {
|
|
18
|
+
cursor: '',
|
|
19
|
+
limit: 100,
|
|
20
|
+
filter: { visibility: 'ALL' },
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **The whole API, not a curated subset.** Types are generated from the official
|
|
25
|
+
OpenAPI spec (API version 2.1): 458 operations, 2083 schemas. Every path is called
|
|
26
|
+
the same way, with the request body and response inferred from the path literal.
|
|
27
|
+
- **Zero runtime dependencies.** Native `fetch` only. Node.js >= 18, CJS and ESM.
|
|
28
|
+
- **A built-in rate limiter that knows Ozon's limits** — 50 rps per Client-Id plus the
|
|
29
|
+
per-method rates from the spec — as a separate subpath: skip it and it costs nothing.
|
|
30
|
+
- **Careful retries.** Nearly every Ozon operation is a POST, so only calls rejected
|
|
31
|
+
with 429 or "Circle is open" are retried: those the API demonstrably did not process.
|
|
32
|
+
Retrying "just in case" is deliberately not a thing this client does.
|
|
33
|
+
|
|
34
|
+
Status: 0.x — the public API may still change between minor releases. Not affiliated
|
|
35
|
+
with Ozon.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install artefactby-ozon-seller-api
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
One client instance is bound to one Client-Id. The package never reads credentials from
|
|
44
|
+
the environment on its own — pass them to the constructor explicitly.
|
|
45
|
+
|
|
46
|
+
## Rate limiting
|
|
47
|
+
|
|
48
|
+
Ozon answers 429 with `Retry-After` when a limit is exceeded and blocks a method for a
|
|
49
|
+
few minutes ("Circle is open") after a burst. The client honours `Retry-After` on its
|
|
50
|
+
own and repeats a rejected call up to `maxRetries` times (2 by default). To avoid
|
|
51
|
+
hitting the limits at all, install the built-in limiter — it paces calls in advance:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { OzonClient } from 'artefactby-ozon-seller-api';
|
|
55
|
+
import { TokenBucketLimiter } from 'artefactby-ozon-seller-api/limiter';
|
|
56
|
+
|
|
57
|
+
const client = new OzonClient({
|
|
58
|
+
clientId,
|
|
59
|
+
apiKey,
|
|
60
|
+
limiter: new TokenBucketLimiter({
|
|
61
|
+
// The defaults are exactly what Ozon documents: 50 rps globally plus the
|
|
62
|
+
// per-method rates from the spec (e.g. /v2/products/stocks — 80 per minute).
|
|
63
|
+
// Add limits you have measured yourself:
|
|
64
|
+
perPath: { '/v3/product/list': { limit: 20, intervalMs: 1_000 } },
|
|
65
|
+
maxSize: 5_000, // reject calls once the queue is this deep
|
|
66
|
+
waitTimeoutMs: 30_000, // reject calls that wait longer
|
|
67
|
+
hooks: {
|
|
68
|
+
onEnqueue: ({ size }) => metrics.gauge('ozon.queue', size),
|
|
69
|
+
onRateLimited: ({ path, until }) => log.warn({ path, until }, 'ozon 429'),
|
|
70
|
+
onCircuitOpen: ({ path, until }) => log.error({ path, until }, 'circle is open'),
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Calls are served by priority (`priority` in the call options, higher first), then by
|
|
77
|
+
arrival. A path stuck at its own limit does not block the others. Backpressure surfaces
|
|
78
|
+
as a typed `OzonQueueError` with a `reason` of `queue-full`, `wait-timeout`, or
|
|
79
|
+
`aborted`.
|
|
80
|
+
|
|
81
|
+
The limiter is in-process. Several processes sharing one Client-Id each get their own
|
|
82
|
+
budget — for a limit that spans instances, implement the `OzonRateLimiter` interface
|
|
83
|
+
over shared storage:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import type { OzonRateLimiter } from 'artefactby-ozon-seller-api';
|
|
87
|
+
|
|
88
|
+
const redisLimiter: OzonRateLimiter = {
|
|
89
|
+
async acquire({ path, priority, signal }) {
|
|
90
|
+
/* wait for a slot in Redis */
|
|
91
|
+
},
|
|
92
|
+
notify({ path, status, retryAfterMs, circuitOpen }) {
|
|
93
|
+
/* record the backoff so every instance sees it */
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Error handling
|
|
99
|
+
|
|
100
|
+
A non-2xx response throws an `OzonApiError`; transport failures (DNS, TLS, dropped
|
|
101
|
+
connections) propagate untouched — the package does not wrap them.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { isOzonApiError } from 'artefactby-ozon-seller-api';
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await client.request('/v1/product/import/prices', { prices });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (isOzonApiError(error)) {
|
|
110
|
+
error.status; // HTTP status, e.g. 429
|
|
111
|
+
error.code; // Ozon's own error code, when the response carried one
|
|
112
|
+
error.retryAfterMs; // parsed Retry-After, when present
|
|
113
|
+
error.body; // response payload (object, or the raw text)
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Options
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const client = new OzonClient({
|
|
123
|
+
clientId,
|
|
124
|
+
apiKey,
|
|
125
|
+
baseUrl: 'https://api-seller.ozon.ru', // default
|
|
126
|
+
fetch: customFetch, // default: the global fetch
|
|
127
|
+
headers: { 'User-Agent': 'my-app/1.0' }, // sent with every request
|
|
128
|
+
timeoutMs: 30_000, // off by default
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await client.request('/v1/actions', undefined, {
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
timeoutMs: 5_000, // overrides the client-level timeout; 0 disables it
|
|
134
|
+
headers: { 'X-Request-Id': id },
|
|
135
|
+
priority: 10, // for the limiter: higher goes first
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The injectable `fetch` is the seam for tests: pass your own implementation to record
|
|
140
|
+
and replay traffic. The package ships no mocks of its own.
|
|
141
|
+
|
|
142
|
+
## Requests beyond the types
|
|
143
|
+
|
|
144
|
+
`request()` covers the JSON operations. For everything else there is `requestRaw()` —
|
|
145
|
+
it returns the untouched `Response` and does not throw on a non-2xx status:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
// PDF with posting labels
|
|
149
|
+
const response = await client.requestRaw('/v2/posting/fbs/package-label', {
|
|
150
|
+
posting_number: ['0001-1'],
|
|
151
|
+
});
|
|
152
|
+
const pdf = await response.blob();
|
|
153
|
+
|
|
154
|
+
// The spec's single templated path needs an explicit method: with the guid
|
|
155
|
+
// substituted, the URL no longer matches the spec literal
|
|
156
|
+
const label = await client.requestRaw(`/v1/cargoes-label/file/${guid}`, undefined, {
|
|
157
|
+
method: 'GET',
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`multipart/form-data` (pass a `FormData`), binary bodies and `ReadableStream`s go
|
|
162
|
+
through as-is; anything else is serialized as JSON. A stream is never retried — it is
|
|
163
|
+
consumed by the first attempt that sends it.
|
|
164
|
+
|
|
165
|
+
## Pagination is yours
|
|
166
|
+
|
|
167
|
+
The package deliberately stops at the transport line: one call is one logical API
|
|
168
|
+
request. Pagination loops, dataset assembly and batch splitting are your code, and the
|
|
169
|
+
typed core leaves them very little to do:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const items = [];
|
|
173
|
+
let cursor = '';
|
|
174
|
+
do {
|
|
175
|
+
const page = await client.request('/v5/product/info/prices', {
|
|
176
|
+
cursor,
|
|
177
|
+
limit: 1000,
|
|
178
|
+
filter: { visibility: 'ALL' },
|
|
179
|
+
});
|
|
180
|
+
items.push(...(page.items ?? []));
|
|
181
|
+
cursor = page.cursor ?? '';
|
|
182
|
+
} while (cursor !== '');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Known limitations
|
|
186
|
+
|
|
187
|
+
- The types are exactly as accurate as Ozon's spec. Where it diverges from reality,
|
|
188
|
+
`requestRaw()` is the way out.
|
|
189
|
+
- The spec is updated in the package manually — brand-new API methods may lag behind.
|
|
190
|
+
- The root entry's type declarations weigh about 3 MB (that is the entire spec); the
|
|
191
|
+
type checker's first pass will notice. The `/limiter` subpath is free of them.
|
|
192
|
+
- The built-in limiter does not share its budget across processes (see above).
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
[MIT](./LICENSE)
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# artefactby-ozon-seller-api
|
|
2
|
+
|
|
3
|
+
Неофициальный типизированный клиент [Ozon Seller API](https://docs.ozon.ru/api/seller/)
|
|
4
|
+
для TypeScript и JavaScript.
|
|
5
|
+
|
|
6
|
+
[README in English](./README.en.md)
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { OzonClient } from 'artefactby-ozon-seller-api';
|
|
10
|
+
|
|
11
|
+
const client = new OzonClient({
|
|
12
|
+
clientId: process.env.OZON_CLIENT_ID!,
|
|
13
|
+
apiKey: process.env.OZON_API_KEY!,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// `items` типизирован из литерала пути — без generics и приведения типов
|
|
17
|
+
const { items } = await client.request('/v5/product/info/prices', {
|
|
18
|
+
cursor: '',
|
|
19
|
+
limit: 100,
|
|
20
|
+
filter: { visibility: 'ALL' },
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Покрыт весь API, а не выборка методов.** Типы сгенерированы из официальной
|
|
25
|
+
OpenAPI-спеки (версия API 2.1): 458 операций, 2083 схемы. Любой путь спеки
|
|
26
|
+
вызывается одинаково, тело запроса и ответ выводятся из литерала пути.
|
|
27
|
+
- **Ноль зависимостей в рантайме.** Только нативный `fetch`. Node.js >= 18, CJS и ESM.
|
|
28
|
+
- **Встроенный rate limiter со знанием лимитов Ozon** — 50 rps на Client-Id плюс
|
|
29
|
+
пометодные лимиты из спеки — отдельным сабпатом: не подключили — не платите ничего.
|
|
30
|
+
- **Осторожные ретраи.** Почти все операции Ozon — POST, поэтому повторяются только
|
|
31
|
+
запросы, отклонённые с 429 или «Circle is open»: их API гарантированно не обработал.
|
|
32
|
+
«Повторить на всякий случай» клиент не умеет намеренно.
|
|
33
|
+
|
|
34
|
+
Статус: 0.x — публичный API может меняться между минорными версиями. Пакет не
|
|
35
|
+
аффилирован с Ozon.
|
|
36
|
+
|
|
37
|
+
## Установка
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install artefactby-ozon-seller-api
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Один экземпляр клиента привязан к одному Client-Id. Пакет никогда не читает ключи из
|
|
44
|
+
окружения сам — передавайте их явно в конструктор.
|
|
45
|
+
|
|
46
|
+
## Ограничение частоты запросов
|
|
47
|
+
|
|
48
|
+
Ozon отвечает 429 с `Retry-After` при превышении лимита и блокирует метод на несколько
|
|
49
|
+
минут («Circle is open») при шквале запросов. Клиент сам выдерживает `Retry-After` и
|
|
50
|
+
повторяет отклонённый запрос до `maxRetries` раз (по умолчанию 2). Чтобы не упираться
|
|
51
|
+
в лимиты вовсе, подключите встроенный лимитер — он выстраивает вызовы заранее:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { OzonClient } from 'artefactby-ozon-seller-api';
|
|
55
|
+
import { TokenBucketLimiter } from 'artefactby-ozon-seller-api/limiter';
|
|
56
|
+
|
|
57
|
+
const client = new OzonClient({
|
|
58
|
+
clientId,
|
|
59
|
+
apiKey,
|
|
60
|
+
limiter: new TokenBucketLimiter({
|
|
61
|
+
// Дефолты — ровно то, что документирует Ozon: 50 rps глобально плюс
|
|
62
|
+
// пометодные лимиты из спеки (например, /v2/products/stocks — 80 в минуту).
|
|
63
|
+
// Сюда можно добавить лимиты, измеренные вами самостоятельно:
|
|
64
|
+
perPath: { '/v3/product/list': { limit: 20, intervalMs: 1_000 } },
|
|
65
|
+
maxSize: 5_000, // отклонять вызовы, когда очередь глубже
|
|
66
|
+
waitTimeoutMs: 30_000, // отклонять вызовы, ждущие дольше
|
|
67
|
+
hooks: {
|
|
68
|
+
onEnqueue: ({ size }) => metrics.gauge('ozon.queue', size),
|
|
69
|
+
onRateLimited: ({ path, until }) => log.warn({ path, until }, 'ozon 429'),
|
|
70
|
+
onCircuitOpen: ({ path, until }) => log.error({ path, until }, 'circle is open'),
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Вызовы обслуживаются по приоритету (`priority` в опциях вызова, выше — раньше), внутри
|
|
77
|
+
приоритета — по очереди. Путь, упёршийся в собственный лимит, не блокирует остальные.
|
|
78
|
+
Backpressure приходит типизированной ошибкой `OzonQueueError` с `reason`:
|
|
79
|
+
`queue-full`, `wait-timeout` или `aborted`.
|
|
80
|
+
|
|
81
|
+
Лимитер работает в пределах одного процесса. Несколько процессов с одним Client-Id
|
|
82
|
+
получат каждый свой бюджет — для общего лимита реализуйте интерфейс `OzonRateLimiter`
|
|
83
|
+
поверх разделяемого хранилища:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import type { OzonRateLimiter } from 'artefactby-ozon-seller-api';
|
|
87
|
+
|
|
88
|
+
const redisLimiter: OzonRateLimiter = {
|
|
89
|
+
async acquire({ path, priority, signal }) {
|
|
90
|
+
/* дождаться слота в Redis */
|
|
91
|
+
},
|
|
92
|
+
notify({ path, status, retryAfterMs, circuitOpen }) {
|
|
93
|
+
/* записать backoff, чтобы его увидели все инстансы */
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Обработка ошибок
|
|
99
|
+
|
|
100
|
+
Не-2xx ответ — это `OzonApiError`; транспортные сбои (DNS, TLS, обрыв) пролетают как
|
|
101
|
+
есть, их пакет не заворачивает.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { isOzonApiError } from 'artefactby-ozon-seller-api';
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await client.request('/v1/product/import/prices', { prices });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (isOzonApiError(error)) {
|
|
110
|
+
error.status; // HTTP-статус, например 429
|
|
111
|
+
error.code; // код ошибки Ozon, если был в ответе
|
|
112
|
+
error.retryAfterMs; // разобранный Retry-After, если был
|
|
113
|
+
error.body; // тело ответа (объект или сырой текст)
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Опции
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const client = new OzonClient({
|
|
123
|
+
clientId,
|
|
124
|
+
apiKey,
|
|
125
|
+
baseUrl: 'https://api-seller.ozon.ru', // по умолчанию
|
|
126
|
+
fetch: customFetch, // по умолчанию — глобальный fetch
|
|
127
|
+
headers: { 'User-Agent': 'my-app/1.0' }, // добавляются к каждому запросу
|
|
128
|
+
timeoutMs: 30_000, // выключен по умолчанию
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await client.request('/v1/actions', undefined, {
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
timeoutMs: 5_000, // важнее клиентского; 0 отключает
|
|
134
|
+
headers: { 'X-Request-Id': id },
|
|
135
|
+
priority: 10, // для лимитера: выше — раньше
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Инжектируемый `fetch` — это шов для тестов: подставьте свою реализацию для записи и
|
|
140
|
+
воспроизведения трафика. Собственных моков пакет не содержит.
|
|
141
|
+
|
|
142
|
+
## Запросы мимо типов
|
|
143
|
+
|
|
144
|
+
`request()` покрывает JSON-операции. Для остального есть `requestRaw()` — он возвращает
|
|
145
|
+
нетронутый `Response` и не бросает на не-2xx статусе:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
// PDF с этикетками отправлений
|
|
149
|
+
const response = await client.requestRaw('/v2/posting/fbs/package-label', {
|
|
150
|
+
posting_number: ['0001-1'],
|
|
151
|
+
});
|
|
152
|
+
const pdf = await response.blob();
|
|
153
|
+
|
|
154
|
+
// Единственный путь спеки с параметром в URL требует явного метода:
|
|
155
|
+
// подставленный guid уже не совпадает с литералом из спеки
|
|
156
|
+
const label = await client.requestRaw(`/v1/cargoes-label/file/${guid}`, undefined, {
|
|
157
|
+
method: 'GET',
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`multipart/form-data` (передайте `FormData`), бинарные тела и `ReadableStream`
|
|
162
|
+
уходят как есть; всё остальное сериализуется в JSON. Стрим не ретраится никогда —
|
|
163
|
+
его потребляет первая же попытка отправки.
|
|
164
|
+
|
|
165
|
+
## Пагинация — на вашей стороне
|
|
166
|
+
|
|
167
|
+
Пакет сознательно заканчивается на границе транспорта: один вызов — один логический
|
|
168
|
+
запрос к API. Циклы пагинации, сборка датасетов, нарезка на батчи — это ваш код,
|
|
169
|
+
которому типизированное ядро оставляет совсем немного работы:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const items = [];
|
|
173
|
+
let cursor = '';
|
|
174
|
+
do {
|
|
175
|
+
const page = await client.request('/v5/product/info/prices', {
|
|
176
|
+
cursor,
|
|
177
|
+
limit: 1000,
|
|
178
|
+
filter: { visibility: 'ALL' },
|
|
179
|
+
});
|
|
180
|
+
items.push(...(page.items ?? []));
|
|
181
|
+
cursor = page.cursor ?? '';
|
|
182
|
+
} while (cursor !== '');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Известные ограничения
|
|
186
|
+
|
|
187
|
+
- Типы ровно настолько точны, насколько точна спека Ozon. Там, где она расходится с
|
|
188
|
+
реальностью, выручает `requestRaw()`.
|
|
189
|
+
- Спека обновляется в пакете вручную — новые методы API могут появляться с задержкой.
|
|
190
|
+
- Декларации типов корневого входа весят около 3 МБ (это вся спека): первый проход
|
|
191
|
+
тайпчекера их заметит. Сабпат `/limiter` от этого свободен.
|
|
192
|
+
- Встроенный лимитер не разделяет бюджет между процессами (см. выше).
|
|
193
|
+
|
|
194
|
+
## Лицензия
|
|
195
|
+
|
|
196
|
+
[MIT](./LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.js';
|