@brigadasos/nadeshiko-sdk 1.5.1-dev.e889ab8 → 2.0.0-dev.384ecdc

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 CHANGED
@@ -5,41 +5,54 @@ TypeScript SDK for the [Nadeshiko API](https://nadeshiko.co).
5
5
  ## Install
6
6
 
7
7
  ```bash
8
+ # npm / pnpm / bun
9
+ npm add @brigadasos/nadeshiko-sdk
10
+ pnpm add @brigadasos/nadeshiko-sdk
8
11
  bun add @brigadasos/nadeshiko-sdk
9
12
  ```
10
13
 
11
- Install the internal build (includes internal + session-authenticated endpoints) via the `internal` dist-tag:
14
+ Install the internal build (includes session-authenticated endpoints) via the `internal` dist-tag:
12
15
 
13
16
  ```bash
14
17
  bun add @brigadasos/nadeshiko-sdk@internal
15
18
  ```
16
19
 
20
+ ## Quick start
21
+
22
+ ```typescript
23
+ import { createNadeshikoClient } from '@brigadasos/nadeshiko-sdk';
24
+
25
+ const client = createNadeshikoClient({
26
+ apiKey: process.env.NADESHIKO_API_KEY!,
27
+ });
28
+
29
+ const { data } = await client.search({ body: { query: { search: '彼女' } } });
30
+ console.log(data.segments);
31
+ ```
32
+
33
+ Errors throw by default. Wrap calls you want to handle explicitly in `try/catch`.
34
+
17
35
  ## Authentication
18
36
 
19
37
  ### API key (server-to-server)
20
38
 
21
- Use an API key for endpoints that don't require a user session. The key is sent as `Authorization: Bearer <apiKey>`.
39
+ Use an API key for public endpoints. The key is sent as `Authorization: Bearer <apiKey>`.
22
40
 
23
41
  ```typescript
24
- import { createNadeshikoClient, search } from '@brigadasos/nadeshiko-sdk';
42
+ import { createNadeshikoClient } from '@brigadasos/nadeshiko-sdk';
25
43
 
26
44
  const client = createNadeshikoClient({
27
45
  apiKey: process.env.NADESHIKO_API_KEY!,
28
- baseUrl: 'PRODUCTION',
29
- });
30
-
31
- const result = await search({
32
- client,
33
- body: { query: { search: '彼女' } },
46
+ baseURL: 'PRODUCTION', // 'LOCAL' | 'DEVELOPMENT' | 'PRODUCTION' | custom URL
34
47
  });
35
48
  ```
36
49
 
37
50
  ### Session token (user-authenticated endpoints, internal build only)
38
51
 
39
- The default public package intentionally exposes API-key-capable endpoints only.
40
- For session-authenticated endpoints (for example `/v1/user/*` and `/v1/collections/*`), use the internal build.
52
+ The public package exposes only API-key-capable endpoints.
53
+ For session-authenticated endpoints (`/v1/user/*`, `/v1/collections/*`), use the internal build.
41
54
 
42
- Endpoints under `/v1/user/*` and `/v1/collections/*` require a user session. Pass a `sessionToken` getter that returns the value of the `nadeshiko.session_token` cookie — called fresh on every request.
55
+ Pass a `sessionToken` getter that returns the value of the `nadeshiko.session_token` cookie — called fresh on every request.
43
56
 
44
57
  **Nuxt / Nitro server routes:**
45
58
 
@@ -63,100 +76,142 @@ export default defineEventHandler(async (event) => {
63
76
  });
64
77
  ```
65
78
 
66
- **Browser note:** if your session cookie is `HttpOnly`, use same-origin proxy routes and let the browser attach cookies automatically. Prefer server-side session handling for internal endpoints.
79
+ **Browser note:** if your session cookie is `HttpOnly`, use same-origin proxy routes and let the browser attach cookies automatically.
67
80
 
68
- ### Error handling
81
+ ## Error handling
69
82
 
70
- Every response returns a discriminated union with either `data` or `error`. The `error` object follows the [RFC 7807](https://tools.ietf.org/html/rfc7807) Problem Details format, so you always get a machine-readable `code` and a human-readable `detail`.
83
+ Errors throw a `NadeshikoError` a proper `Error` subclass with all RFC 7807 Problem Details fields available directly.
71
84
 
72
85
  ```typescript
73
- import { createNadeshikoClient, search } from '@brigadasos/nadeshiko-sdk';
86
+ import { createNadeshikoClient, NadeshikoError } from '@brigadasos/nadeshiko-sdk';
74
87
 
75
- const client = createNadeshikoClient({
76
- apiKey: process.env.NADESHIKO_API_KEY!,
77
- baseUrl: 'PRODUCTION',
78
- });
88
+ const client = createNadeshikoClient({ apiKey: process.env.NADESHIKO_API_KEY! });
89
+
90
+ try {
91
+ const { data } = await client.search({ body: { query: { search: '食べる' } } });
92
+ console.log(data.segments);
93
+ } catch (err) {
94
+ if (err instanceof NadeshikoError) {
95
+ switch (err.code) {
96
+ // 400 — Bad Request
97
+ case 'VALIDATION_FAILED':
98
+ console.error('Validation failed:', err.detail);
99
+ for (const [field, msg] of Object.entries(err.errors ?? {})) {
100
+ console.error(` ${field}: ${msg}`);
101
+ }
102
+ break;
103
+ case 'INVALID_JSON':
104
+ case 'INVALID_REQUEST':
105
+ console.error('Bad request:', err.detail);
106
+ break;
107
+
108
+ // 401 — Unauthorized
109
+ case 'AUTH_CREDENTIALS_REQUIRED':
110
+ console.error('Missing API key');
111
+ break;
112
+ case 'AUTH_CREDENTIALS_INVALID':
113
+ console.error('API key is invalid');
114
+ break;
115
+ case 'AUTH_CREDENTIALS_EXPIRED':
116
+ console.error('Token has expired, re-authenticate');
117
+ break;
118
+
119
+ // 403 — Forbidden
120
+ case 'ACCESS_DENIED':
121
+ case 'INSUFFICIENT_PERMISSIONS':
122
+ console.error('Access denied');
123
+ break;
124
+
125
+ // 429 — Too Many Requests
126
+ case 'RATE_LIMIT_EXCEEDED':
127
+ console.error('Rate limit hit, slow down');
128
+ break;
129
+ case 'QUOTA_EXCEEDED':
130
+ console.error('Monthly quota exhausted');
131
+ break;
132
+
133
+ // 500 — Internal Server Error
134
+ case 'INTERNAL_SERVER_EXCEPTION':
135
+ // err.traceId is the instance field — include when reporting issues
136
+ console.error('Server error, trace ID:', err.traceId);
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ ```
79
142
 
80
- const result = await search({
81
- client,
82
- body: { query: { search: '食べる' } },
143
+ **`NadeshikoError` fields:**
144
+
145
+ | Field | Type | Description |
146
+ |---|---|---|
147
+ | `code` | `string` | Machine-readable error code |
148
+ | `title` | `string` | Short summary |
149
+ | `detail` | `string` | Human-readable explanation |
150
+ | `status` | `number` | HTTP status code |
151
+ | `traceId` | `string \| undefined` | Trace ID for this error — include when reporting issues |
152
+ | `errors` | `Record<string, string> \| undefined` | Per-field messages (`VALIDATION_FAILED` only) |
153
+
154
+ ### Opt out of throwing per-call
155
+
156
+ If you need the old `{ data, error }` return shape for a specific call, pass `throwOnError: false`:
157
+
158
+ ```typescript
159
+ const result = await client.search({
160
+ throwOnError: false,
161
+ body: { query: { search: '猫' } },
83
162
  });
84
163
 
85
164
  if (result.error) {
86
- switch (result.error.code) {
87
- // 400 — Bad Request
88
- case 'VALIDATION_FAILED':
89
- console.error('Validation failed:', result.error.detail);
90
- for (const [field, msg] of Object.entries(result.error.errors ?? {})) {
91
- console.error(` ${field}: ${msg}`);
92
- }
93
- break;
94
- case 'INVALID_JSON':
95
- console.error('Malformed JSON body:', result.error.detail);
96
- break;
97
- case 'INVALID_REQUEST':
98
- console.error('Invalid request:', result.error.detail);
99
- break;
100
-
101
- // 401 — Unauthorized
102
- case 'AUTH_CREDENTIALS_REQUIRED':
103
- console.error('Missing API key or session token');
104
- break;
105
- case 'AUTH_CREDENTIALS_INVALID':
106
- console.error('API key is invalid');
107
- break;
108
- case 'AUTH_CREDENTIALS_EXPIRED':
109
- console.error('Token has expired, re-authenticate');
110
- break;
111
- case 'EMAIL_NOT_VERIFIED':
112
- console.error('Email verification required');
113
- break;
114
-
115
- // 403 — Forbidden
116
- case 'ACCESS_DENIED':
117
- console.error('Access denied');
118
- break;
119
- case 'INSUFFICIENT_PERMISSIONS':
120
- console.error('API key lacks the required scope');
121
- break;
122
-
123
- // 429 — Too Many Requests
124
- case 'RATE_LIMIT_EXCEEDED':
125
- console.error('Rate limit hit, slow down');
126
- break;
127
- case 'QUOTA_EXCEEDED':
128
- console.error('Monthly quota exhausted');
129
- break;
130
-
131
- // 500 — Internal Server Error
132
- case 'INTERNAL_SERVER_EXCEPTION':
133
- console.error('Server error, trace ID:', result.error.instance);
134
- break;
135
- }
136
- return;
165
+ console.error(result.error);
166
+ } else {
167
+ console.log(result.data.segments);
137
168
  }
169
+ ```
138
170
 
139
- // result.data is fully typed as SearchResponse
140
- for (const segment of result.data.segments ?? []) {
141
- console.log(segment.content.ja.content);
142
- }
171
+ ## Retry and timeout
172
+
173
+ The client retries automatically on network errors and `408 / 429 / 500 / 502 / 503 / 504` responses. `429` responses with a `Retry-After` header are respected. Configure via `retryOptions`:
174
+
175
+ ```typescript
176
+ const client = createNadeshikoClient({
177
+ apiKey: process.env.NADESHIKO_API_KEY!,
178
+ retryOptions: {
179
+ maxRetries: 3, // default: 2
180
+ initialDelayMs: 1000, // default: 500 — doubles with each attempt
181
+ maxDelayMs: 30_000, // default: 30_000
182
+ timeout: 10_000, // per-attempt timeout in ms (default: none)
183
+ },
184
+ });
143
185
  ```
144
186
 
145
- ### `throwOnError` mode
187
+ ## Pagination
146
188
 
147
- If you prefer exceptions over checking `.error`, pass `throwOnError: true`. The call will throw on any non-2xx response, and the return type narrows to just `{ data }`.
189
+ Use `paginate()` to iterate through all pages without manual cursor tracking:
148
190
 
149
191
  ```typescript
150
- try {
151
- const { data } = await search({
152
- client,
153
- throwOnError: true,
154
- body: { query: { search: '彼女' } },
155
- });
192
+ import { createNadeshikoClient, paginate } from '@brigadasos/nadeshiko-sdk';
156
193
 
157
- console.log(data.segments);
158
- } catch (error) {
159
- console.error('Request failed:', error);
194
+ const client = createNadeshikoClient({ apiKey: process.env.NADESHIKO_API_KEY! });
195
+
196
+ for await (const segment of paginate(
197
+ (opts) => client.search(opts),
198
+ { body: { query: { search: '猫' } } },
199
+ (data) => ({ items: data.segments, pagination: data.pagination }),
200
+ )) {
201
+ console.log(segment.textJa.content);
202
+ }
203
+ ```
204
+
205
+ `paginate()` works with any endpoint that returns `{ pagination: { hasMore, cursor } }`:
206
+
207
+ ```typescript
208
+ // Browse all media
209
+ for await (const media of paginate(
210
+ (opts) => client.listMedia(opts),
211
+ {},
212
+ (data) => ({ items: data.media, pagination: data.pagination }),
213
+ )) {
214
+ console.log(media.nameEn);
160
215
  }
161
216
  ```
162
217
 
@@ -0,0 +1,48 @@
1
+ import type { Error400, Error401, Error403, Error429, Error500, Error409, Error404 } from './types.gen';
2
+ /** Union of all known API error codes. */
3
+ export type NadeshikoErrorCode = Error400['code'] | Error401['code'] | Error403['code'] | Error429['code'] | Error500['code'] | Error409['code'] | Error404['code'];
4
+ export interface NadeshikoProblemDetails {
5
+ code: NadeshikoErrorCode;
6
+ title: string;
7
+ detail: string;
8
+ type?: string;
9
+ /** Trace ID for this specific error occurrence — include when reporting issues */
10
+ instance?: string;
11
+ status: number;
12
+ /** Per-field validation messages, present when `code` is `'VALIDATION_FAILED'` */
13
+ errors?: Record<string, string>;
14
+ }
15
+ /**
16
+ * Thrown by the SDK when the API returns a non-2xx response.
17
+ *
18
+ * All fields from the RFC 7807 Problem Details response body are
19
+ * available directly on the error instance.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { NadeshikoError } from '@brigadasos/nadeshiko-sdk';
24
+ *
25
+ * try {
26
+ * const { data } = await client.search({ body: { query: { search: '猫' } } });
27
+ * } catch (err) {
28
+ * if (err instanceof NadeshikoError) {
29
+ * console.error(err.code); // 'RATE_LIMIT_EXCEEDED'
30
+ * console.error(err.status); // 429
31
+ * console.error(err.traceId); // trace ID for support
32
+ * }
33
+ * }
34
+ * ```
35
+ */
36
+ export declare class NadeshikoError extends Error {
37
+ readonly code: NadeshikoErrorCode;
38
+ readonly title: string;
39
+ readonly detail: string;
40
+ readonly type?: string;
41
+ readonly status: number;
42
+ /** Trace ID from `instance` field — include when reporting issues */
43
+ readonly traceId?: string;
44
+ /** Per-field validation messages, present when `code === 'VALIDATION_FAILED'` */
45
+ readonly errors?: Record<string, string>;
46
+ constructor(body: NadeshikoProblemDetails);
47
+ }
48
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../generated/dev/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAExG,0CAA0C;AAC1C,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEpK,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAE7B,IAAI,EAAE,uBAAuB;CAW1C"}