@nxgt/datasource-rest 1.0.3 → 3.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
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.md CHANGED
@@ -1,87 +1,386 @@
1
1
  # @nxgt/datasource-rest
2
2
 
3
- An Apollo-style REST datasource over `openapi-fetch`: auth forwarding, caching
4
- and error translation, so a GraphQL resolver can call a REST service with the
5
- same typed client the REST apps use.
3
+ A REST service, called from a GraphQL resolver or any server code, through
4
+ the client [`@nxgt/openapi-httpyz`](https://www.npmjs.com/package/@nxgt/openapi-httpyz)
5
+ binds to its spec. On top of that client it adds three things:
6
+
7
+ - it forwards the caller's token;
8
+ - it caches reads across the datasources of every request;
9
+ - it throws one `DataSourceError`, with a code a GraphQL server reports,
10
+ whenever a call fails.
6
11
 
7
12
  ## Install
8
13
 
9
14
  ```bash
10
- bun add @nxgt/datasource-rest
15
+ bun add @nxgt/datasource-rest @nxgt/httpyz @nxgt/openapi-httpyz zod
16
+ bun add -d @nxgt/openapi-codegen typescript
11
17
  ```
12
18
 
13
- Public on npmjs; no token needed to install. TypeScript is a peer, pinned to
14
- `^6.0.3` across every `@nxgt/*` package — the set is unsatisfiable if one of
15
- them widens it.
19
+ `@nxgt/httpyz` and `@nxgt/openapi-httpyz` are required peers. The generated
20
+ `operations.ts` imports `zod`, so the app needs it too. TypeScript is a peer,
21
+ `^6.0.3`, as in every `@nxgt/*` package. `@nxgt/openapi-codegen` generates
22
+ the spec's files, with `nxgt-openapi generate`.
16
23
 
17
24
  ## Usage
18
25
 
26
+ Extend the datasource with the calls your resolvers make, and make one per
27
+ request:
28
+
19
29
  ```ts
20
- import { RESTDataSource, errorToException } from '@nxgt/datasource-rest';
21
- import createClient from '@nxgt/shared-hono/openapi-fetch';
30
+ import { RESTDataSource } from '@nxgt/datasource-rest';
31
+ import { operations } from './generated/bookmarks/operations.js';
22
32
 
23
- const client = createClient<paths>({ baseUrl: env.BOOKMARKS_API_URL });
33
+ export class Bookmarks extends RESTDataSource.for(operations) {
34
+ bookmark(id: string) {
35
+ return this.data(this.get('/bookmarks/{id}', { param: { id } }));
36
+ }
37
+ search(q: string) {
38
+ return this.data(this.query('/bookmarks', { json: { q } }));
39
+ }
40
+ }
24
41
 
25
- const bookmarks = new RESTDataSource({
26
- client,
27
- authOptions: { token: () => getAccessToken() },
28
- cacheOptions: { ttl: 5_000 },
42
+ // Per request, in the GraphQL context:
43
+ const bookmarks = new Bookmarks({
44
+ baseUrl: 'https://bookmarks.example.com',
45
+ token: () => context.token,
29
46
  });
47
+ ```
30
48
 
31
- const { data } = await bookmarks.get('/bookmarks/{id}', {
32
- params: { path: { id } },
33
- });
49
+ - **`RESTDataSource.for(operations)`** is the class bound to the generated
50
+ table, whose types it takes from it. The same without it:
51
+ `extends RESTDataSource<ClientOperations>`, with `operations` passed to the
52
+ constructor.
53
+ - **It validates and decodes by default**, as its client does: the request
54
+ before it is sent, and each reply as its schema outputs it.
55
+ `validate: false` and `decode: false` turn that off.
56
+ - **`this.get`, `this.post`, `this.query`…** call the operation at a path,
57
+ typed by the spec. There is one for each method the spec has an operation
58
+ for, and it offers only the paths that method has. Each call resolves to
59
+ one of the declared replies, narrowed on its status.
60
+ - **`this.api`** is the bound client they come from, for `op(operationId)`,
61
+ `stream()` and `group()`. The shorthands are set on each datasource, for
62
+ the spec's methods only, so a subclass's own method of the same name wins.
63
+ - **`this.data(call)`** returns the data of a 2xx reply. Any other reply, or
64
+ none, throws a `DataSourceError`.
65
+
66
+ Every option is in [the API](#restdatasource).
67
+
68
+ ## Cache
69
+
70
+ `defaultCache` is one store for every datasource, since a server makes them
71
+ per request. It is `cache()` from `@nxgt/httpyz`, with its defaults: it
72
+ keeps the `ok` replies of the reads, `GET`, `HEAD` and `QUERY`, for five
73
+ minutes. A search is a `QUERY`; a `POST` is never cached.
74
+
75
+ Its key is the method, the URL, the `Authorization` header and the body. So
76
+ no caller is answered with another's reply, and a search is cached by what it
77
+ searches for. It holds at most 500 replies, the least recently used going
78
+ first.
79
+
80
+ `defaultCache.clear()` empties it, after a write, say. A cache of your own
81
+ takes `cache()`'s options; `cache: false` turns it off:
82
+
83
+ ```ts
84
+ import { cache } from '@nxgt/httpyz';
85
+
86
+ const bookmarksCache = cache({ ttl: 30_000 });
87
+ new Bookmarks({ baseUrl, operations, cache: bookmarksCache });
34
88
  ```
35
89
 
36
- `get` / `post` / `put` / `patch` / `delete` / `head` / `options` / `trace` are
37
- the client's methods, rebound. Auth and cache are `openapi-fetch` middleware
38
- installed in the constructor, cache first, then auth.
90
+ ## Errors
91
+
92
+ `this.data` throws a `DataSourceError`, the package's own. graphql-js reports
93
+ its `extensions`, `{ code, status }`, with the error a resolver throws.
94
+
95
+ | What happened | `code` |
96
+ | --- | --- |
97
+ | 401 | `UNAUTHENTICATED` |
98
+ | 403 | `FORBIDDEN` |
99
+ | 404 | `NOT_FOUND` |
100
+ | any 5xx | `INTERNAL_SERVER_ERROR` |
101
+ | any other status | `BAD_REQUEST` |
102
+ | no reply: network, timeout | `SERVICE_UNAVAILABLE` |
103
+ | a request `validate` refuses, with its `issues` | `BAD_REQUEST` |
104
+ | a reply `validate` or `decode` refuses, with its `issues` | `INTERNAL_SERVER_ERROR` |
105
+ | anything else thrown | `INTERNAL_SERVER_ERROR` |
39
106
 
40
- ## Auth middleware
107
+ Its message is the reply body's `message`, when it has a non-empty one. For
108
+ a call you make without `this.data`, `toDataSourceError` makes one out of
109
+ whatever the client threw. To map it onto your app's exceptions, catch it
110
+ where you throw yours:
41
111
 
42
112
  ```ts
43
- authOptions: {
44
- token: string | (() => Promise<string>),
45
- shouldUseToken?: (request: Request) => boolean,
113
+ import { DataSourceError } from '@nxgt/datasource-rest';
114
+
115
+ try {
116
+ return await bookmarks.bookmark(id);
117
+ } catch (error) {
118
+ if (error instanceof DataSourceError && error.code === 'NOT_FOUND') {
119
+ return null;
120
+ }
121
+ throw error;
46
122
  }
47
123
  ```
48
124
 
49
- The thunk is called per request. `shouldUseToken` skips the header on the
50
- requests you name. No `token` means the middleware is a no-op.
125
+ ## Pagination
51
126
 
52
- ## Cache middleware
127
+ `relayPaginate(page)` turns a service's `{ data, metadata }` page into a Relay
128
+ `{ edges, pageInfo }` connection, using `item.id` as the cursor:
53
129
 
54
- In-process `Map`, keyed by `method:url`. Default TTL is five minutes. GET
55
- (and any other non-POST) responses that are `ok` are stored. POST is cached
56
- only when `shouldCachePostRequest` says so — the default is "the URL contains
57
- `/search`".
130
+ ```ts
131
+ import { RESTDataSource, relayPaginate } from '@nxgt/datasource-rest';
58
132
 
59
- This is a process cache, not Redis. Two instances of the datasource do not
60
- share it.
133
+ export class Bookmarks extends RESTDataSource<ClientOperations> {
134
+ async bookmarks(first: number) {
135
+ return relayPaginate(
136
+ await this.data(this.get('/bookmarks', { query: { first } })),
137
+ );
138
+ }
139
+ }
140
+ ```
61
141
 
62
- ## Errors
142
+ ## API
63
143
 
64
- `errorToException(error, response)` maps HTTP status to `CustomException`:
144
+ ### Classes
65
145
 
66
- | Status | `ErrorCode` |
67
- | --- | --- |
68
- | 401 | `Unauthenticated` |
69
- | 403 | `Forbidden` |
70
- | 404 | `NotFound` |
71
- | 500 | `InternalServerError` |
72
- | other | `BadRequest` |
73
-
74
- A downstream 404 is `NotFound`, not a transport failure. Catch
75
- `CustomException`, not an error named after the HTTP library.
76
-
77
- `relayPaginate(data)` turns a REST `{ data, metadata }` page into a Relay
78
- `{ edges, pageInfo }` connection, using `item.id` as the cursor.
79
-
80
- ## Things that bite
81
-
82
- - **The cache is per process and unbounded except by TTL.** Do not point it at
83
- an endpoint that varies by caller unless `shouldUseToken` / the URL already
84
- distinguish them.
85
- - **`authOptions.token` as a thunk is not awaited by the middleware today.**
86
- Pass a string, or a thunk that returns a string synchronously, until that
87
- is a real `async` `onRequest`.
146
+ #### `RESTDataSource`
147
+
148
+ ```ts
149
+ class RESTDataSource<
150
+ Ops extends OperationsShape<Ops>, // ClientOperations, generated
151
+ Routes = RoutesOf<Ops>,
152
+ Decoded extends boolean = true,
153
+ > {
154
+ constructor(options: RESTDataSourceOptions<Ops> & { decode?: Decoded });
155
+ static for(operations: OperationTable<Ops>, options?: { decode?: boolean }): BoundDataSource<Ops, Decoded>;
156
+ readonly api: OpenApiClient<Ops, Routes, Decoded>;
157
+ data<R extends { readonly status: number; readonly data: unknown }>(
158
+ call: Promise<R>,
159
+ ): Promise<Success<R>['data']>;
160
+ // and PathMethods<Ops, Routes, Decoded>: get, post, query…
161
+ }
162
+ ```
163
+
164
+ The class to extend, one per service; see [Usage](#usage). `RESTDataSource`
165
+ is both a constructor and a type, the datasource's own members and the
166
+ spec's path methods. With `Decoded` set to `false`, the options require
167
+ `decode: false`.
168
+
169
+ | Option | Type | Default | Description |
170
+ | --- | --- | --- | --- |
171
+ | `baseUrl` | `string` | required | The service's base URL. |
172
+ | `operations` | `OperationTable<Ops>` | required | The `operations` table generated from the service's spec. |
173
+ | `token` | `string \| null \| undefined \| (() => … \| Promise<…>)` | none | Sent as `Authorization: Bearer <token>`. A function is read, and awaited, before each request. None, or a nullish result, sends no header. |
174
+ | `cache` | `Cache \| false` | `defaultCache` | The replies cache: your own `cache()` from `@nxgt/httpyz`, or `false` for none. See [Cache](#cache). |
175
+ | `http` | `Omit<HttpClientOptions, 'baseUrl' \| 'auth'>` | `{}` | What else the core client takes: `headers`, `timeout`, `retry`, `use`, `fetch`… Its `use` runs inside the cache. |
176
+ | `validate` | `boolean \| { request?: boolean; response?: boolean }` | both | Checks the request and the reply with the spec's schemas, as `createOpenApiClient` does. `false`: neither. |
177
+ | `decode` | `Decoded` | `true` | Returns each reply as its schema outputs it. `false` returns it as JSON carries it. |
178
+
179
+ | Member | Type | Description |
180
+ | --- | --- | --- |
181
+ | `api` | `OpenApiClient<Ops, Routes, Decoded>` | The client bound to the spec, for `op()`, `stream()`, `group()` and `operations`. |
182
+ | `data(call)` | `Promise<Success<R>['data']>` | The data of `call`'s 2xx reply. Any other reply, or none, throws a `DataSourceError`; an abort is rethrown as it is. The body of a status the spec does not declare is read for its message. |
183
+ | `get`, `put`, `post`, `delete`, `options`, `head`, `patch`, `trace`, `query` | `PathMethods<Ops, Routes, Decoded>[M]` | `this.api[method]`, for the methods the spec has an operation for only, in the types and at runtime: `(path, ...args) => Promise<reply>`. Set on each datasource, so a subclass's method of the same name wins. |
184
+
185
+ #### `RESTDataSource.for`
186
+
187
+ ```ts
188
+ RESTDataSource.for<Ops extends OperationsShape<Ops>>(operations: OperationTable<Ops>): BoundDataSource<Ops, true>;
189
+ RESTDataSource.for<Ops extends OperationsShape<Ops>>(
190
+ operations: OperationTable<Ops>,
191
+ options: { readonly decode: false },
192
+ ): BoundDataSource<Ops, false>;
193
+ ```
194
+
195
+ A datasource class bound to the generated `operations` table, which takes
196
+ its types from it: `class Bookmarks extends RESTDataSource.for(operations)`,
197
+ with no `ClientOperations` to import. Its constructor takes every option but
198
+ `operations`. With `{ decode: false }`, its replies are returned as JSON
199
+ carries them. See [Usage](#usage).
200
+
201
+ #### `DataSourceError`
202
+
203
+ ```ts
204
+ class DataSourceError extends Error {
205
+ constructor(message: string, options: DataSourceErrorOptions);
206
+ readonly name: 'DataSourceError';
207
+ readonly code: DataSourceErrorCode;
208
+ readonly status: number | undefined;
209
+ readonly data: unknown;
210
+ readonly issues: readonly ValidationIssue[] | undefined;
211
+ readonly extensions: {
212
+ readonly code: DataSourceErrorCode;
213
+ readonly status: number | undefined;
214
+ };
215
+ }
216
+ ```
217
+
218
+ Every way a call to a service fails; see [Errors](#errors). The constructor
219
+ passes `cause`, and any other `ErrorOptions`, on to `Error`.
220
+
221
+ | Property | Type | Description |
222
+ | --- | --- | --- |
223
+ | `name` | `'DataSourceError'` | |
224
+ | `message` | `string` | The reply body's `message`, or the client error's; `'An error occurred'` when what was thrown is not an `Error`. |
225
+ | `code` | `DataSourceErrorCode` | What failed, from the reply's status or the lack of one. |
226
+ | `status` | `number \| undefined` | The status of the service's reply; none when no reply came back. |
227
+ | `data` | `unknown` | The reply's body, as the service sent it; none when no reply was read. |
228
+ | `issues` | `readonly ValidationIssue[] \| undefined` | What a `validate` or `decode` check refused, in the request or the reply, as `@nxgt/httpyz` has them; none otherwise. |
229
+ | `extensions` | `{ code, status }` | What a GraphQL server reports with the error: graphql-js reads `extensions` off the error a resolver throws. |
230
+ | `cause` | `unknown` | What was thrown, when `toDataSourceError` made the error: the client's own error, or anything else. |
231
+
232
+ ### Functions
233
+
234
+ #### `toDataSourceError`
235
+
236
+ ```ts
237
+ function toDataSourceError(error: unknown, data?: unknown): DataSourceError;
238
+ ```
239
+
240
+ `error` as a `DataSourceError`, whatever the client threw; a
241
+ `DataSourceError` is returned as it is. `data` is the body of a reply the
242
+ error does not carry, an `UndeclaredStatusError`'s, which only a read of its
243
+ response gives. It codes as the table in [Errors](#errors) says, and never
244
+ throws.
245
+
246
+ #### `codeOf`
247
+
248
+ ```ts
249
+ function codeOf(status: number): DataSourceErrorCode;
250
+ ```
251
+
252
+ The code of a reply's status: 401, 403 and 404 by name, any 5xx
253
+ `INTERNAL_SERVER_ERROR`, any other `BAD_REQUEST`.
254
+
255
+ #### `relayPaginate`
256
+
257
+ ```ts
258
+ function relayPaginate<T extends { id: string }>(
259
+ page: Paginated<T>,
260
+ ): Connection<T>;
261
+ ```
262
+
263
+ A service's page as a Relay connection: each item an edge, its `id` the
264
+ cursor, `metadata` the `pageInfo`. A page without `data` has no edges; one
265
+ without `metadata` gets the `pageInfo` of an empty last page,
266
+ `{ hasNextPage: false, totalElements: 0 }`, even when it has items. See
267
+ [Pagination](#pagination).
268
+
269
+ ### Constants
270
+
271
+ #### `defaultCache`
272
+
273
+ ```ts
274
+ const defaultCache: Cache; // Middleware & { clear(): void }
275
+ ```
276
+
277
+ `cache()` from `@nxgt/httpyz` with its defaults, shared by every datasource
278
+ not given its own `cache`. `defaultCache.clear()` empties it. See
279
+ [Cache](#cache).
280
+
281
+ ### Types
282
+
283
+ #### `RESTDataSourceOptions`
284
+
285
+ ```ts
286
+ interface RESTDataSourceOptions<Ops> extends OpenApiOptions {
287
+ baseUrl: string;
288
+ operations: OperationTable<Ops>;
289
+ token?: Token | (() => Token | Promise<Token>); // Token: string | null | undefined
290
+ cache?: Cache | false;
291
+ http?: Omit<HttpClientOptions, 'baseUrl' | 'auth'>;
292
+ }
293
+ ```
294
+
295
+ What the `RESTDataSource` constructor takes, with `decode`; each option is in
296
+ [its table](#restdatasource).
297
+
298
+ #### `BoundDataSource`
299
+
300
+ ```ts
301
+ type BoundDataSource<Ops extends OperationsShape<Ops>, Decoded extends boolean = true> = new (
302
+ options: Omit<RESTDataSourceOptions<Ops>, 'operations'>,
303
+ ) => RESTDataSource<Ops, RoutesOf<Ops>, Decoded>;
304
+ ```
305
+
306
+ What [`RESTDataSource.for`](#restdatasourcefor) returns: a datasource class
307
+ bound to its table, to extend.
308
+
309
+ #### `DataSourceErrorCode`
310
+
311
+ ```ts
312
+ type DataSourceErrorCode =
313
+ | 'BAD_REQUEST'
314
+ | 'UNAUTHENTICATED'
315
+ | 'FORBIDDEN'
316
+ | 'NOT_FOUND'
317
+ | 'INTERNAL_SERVER_ERROR'
318
+ | 'SERVICE_UNAVAILABLE';
319
+ ```
320
+
321
+ A `DataSourceError`'s `code`, and what `codeOf` returns.
322
+
323
+ #### `DataSourceErrorOptions`
324
+
325
+ ```ts
326
+ interface DataSourceErrorOptions extends ErrorOptions {
327
+ readonly code: DataSourceErrorCode;
328
+ readonly status?: number | undefined;
329
+ readonly data?: unknown;
330
+ readonly issues?: readonly ValidationIssue[] | undefined;
331
+ }
332
+ ```
333
+
334
+ The second argument of `new DataSourceError()`.
335
+
336
+ #### `Paginated`
337
+
338
+ | Field | Type | Description |
339
+ | --- | --- | --- |
340
+ | `data` | `T[]`, optional | The page's items. |
341
+ | `metadata` | `PageInfo`, optional | Where the page stands. |
342
+
343
+ A page as a service sends it: what `relayPaginate` takes.
344
+
345
+ #### `Connection`
346
+
347
+ | Field | Type | Description |
348
+ | --- | --- | --- |
349
+ | `edges` | `Edge<T>[]` | One per item. |
350
+ | `pageInfo` | `PageInfo` | Where the page stands. |
351
+
352
+ The Relay connection `relayPaginate` returns.
353
+
354
+ #### `Edge`
355
+
356
+ | Field | Type | Description |
357
+ | --- | --- | --- |
358
+ | `node` | `T` | The item. |
359
+ | `cursor` | `string` | The item's `id`. |
360
+
361
+ An item of a `Connection`.
362
+
363
+ #### `PageInfo`
364
+
365
+ | Field | Type | Description |
366
+ | --- | --- | --- |
367
+ | `startCursor` | `string`, optional | The cursor of the first item in the result set. |
368
+ | `endCursor` | `string`, optional | The cursor of the last item in the result set. |
369
+ | `hasNextPage` | `boolean` | Whether there are more items after the current page. |
370
+ | `totalElements` | `number` | The number of items across all pages. |
371
+
372
+ A `Paginated`'s `metadata`, and a `Connection`'s `pageInfo`, as it is.
373
+
374
+ ## Traps
375
+
376
+ - **The cache is per process.** Two instances of a service do not share it,
377
+ so each may answer from its own for up to five minutes: give reads that must
378
+ be fresh `cache: false`, or a `cache({ ttl })` of your own.
379
+ - **A write does not empty the cache.** A read made after it may be answered
380
+ from memory: call `defaultCache.clear()` after the write.
381
+ - **An aborted call throws its abort**, not a `DataSourceError`: it was not
382
+ the service failing. Tell it apart with `isAbortError(error)` from
383
+ `@nxgt/httpyz`.
384
+ - **`this.data` reads the body of an undeclared status** to find its message.
385
+ A reply the spec declares is already read; declare the error statuses the
386
+ service replies with.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `RESTDataSource`: a REST service, called from a GraphQL resolver or any
3
+ * server code, through the client `@nxgt/openapi-httpyz` binds to its spec.
4
+ * The caller's token is forwarded, reads are cached, and a call that fails
5
+ * becomes one `DataSourceError`.
6
+ */
7
+ import { type Cache, type HttpClientOptions, type Success } from '@nxgt/httpyz';
8
+ import { type OpenApiClient, type OpenApiOptions, type OperationsShape, type OperationTable, type PathMethods, type RoutesOf } from '@nxgt/openapi-httpyz';
9
+ type Token = string | null | undefined;
10
+ /**
11
+ * The cache every datasource shares unless it is given its own: a server
12
+ * makes its datasources per request, and each would otherwise start empty.
13
+ * It keeps the reads, `GET`, `HEAD` and `QUERY`, a search by what it searches
14
+ * for. It is keyed by the caller's token, so no caller is answered with
15
+ * another's reply. `defaultCache.clear()` empties it.
16
+ */
17
+ export declare const defaultCache: Cache;
18
+ export interface RESTDataSourceOptions<Ops> extends OpenApiOptions {
19
+ /** The service's base URL. */
20
+ baseUrl: string;
21
+ /** The `operations` table generated from the service's spec. */
22
+ operations: OperationTable<Ops>;
23
+ /**
24
+ * Sent as `Authorization: Bearer <token>`: the caller's, forwarded. A
25
+ * function is read, and awaited, before each request. None sends no header.
26
+ */
27
+ token?: Token | (() => Token | Promise<Token>);
28
+ /** Default: `defaultCache`, shared by every datasource. `false`: none. */
29
+ cache?: Cache | false;
30
+ /** What else the core client takes: `headers`, `timeout`, `retry`, `use`, `fetch`… */
31
+ http?: Omit<HttpClientOptions, 'baseUrl' | 'auth'>;
32
+ }
33
+ /** `decode`, as the binding takes it: a datasource typed as not decoding must say `decode: false`. */
34
+ type Decoding<Decoded extends boolean> = {
35
+ readonly decode?: Decoded;
36
+ } & (Decoded extends false ? {
37
+ readonly decode: false;
38
+ } : unknown);
39
+ /** The datasource's own members; `RESTDataSource` adds the client's path methods. */
40
+ declare class DataSource<Ops extends OperationsShape<Ops>, Routes, Decoded extends boolean> {
41
+ /** The client bound to the spec, for `op()`, `stream()` and `group()`. */
42
+ readonly api: OpenApiClient<Ops, Routes, Decoded>;
43
+ /**
44
+ * A datasource class bound to `operations`, whose types it takes from the
45
+ * generated table: no `ClientOperations` to import. Its constructor takes
46
+ * the other options.
47
+ */
48
+ static for(operations: object, fixed?: {
49
+ readonly decode?: boolean;
50
+ }): {
51
+ new (options: object): {};
52
+ };
53
+ constructor(options: RESTDataSourceOptions<Ops> & Decoding<Decoded>);
54
+ /**
55
+ * The data of a call's 2xx reply. Any other reply, or none, throws a
56
+ * `DataSourceError`; an aborted call throws its abort, as it is.
57
+ */
58
+ data<R extends {
59
+ readonly status: number;
60
+ readonly data: unknown;
61
+ }>(call: Promise<R>): Promise<Success<R>['data']>;
62
+ }
63
+ /**
64
+ * A datasource for a service, typed by the `ClientOperations` generated from
65
+ * its spec. It has the client's `get`, `post`… for each method the spec has
66
+ * an operation for. Extend it with the calls a resolver makes:
67
+ *
68
+ * ```ts
69
+ * class Bookmarks extends RESTDataSource.for(operations) {
70
+ * bookmark(id: string) {
71
+ * return this.data(this.get('/bookmarks/{id}', { param: { id } }));
72
+ * }
73
+ * }
74
+ * new Bookmarks({ baseUrl, token: () => context.token });
75
+ * ```
76
+ */
77
+ export type RESTDataSource<Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = true> = DataSource<Ops, Routes, Decoded> & PathMethods<Ops, Routes, Decoded>;
78
+ /** What `RESTDataSource.for(operations)` returns: a datasource class bound to the table. */
79
+ export type BoundDataSource<Ops extends OperationsShape<Ops>, Decoded extends boolean = true> = new (options: Omit<RESTDataSourceOptions<Ops>, 'operations'>) => RESTDataSource<Ops, RoutesOf<Ops>, Decoded>;
80
+ export declare const RESTDataSource: {
81
+ new <Ops extends OperationsShape<Ops>, Routes = RoutesOf<Ops>, Decoded extends boolean = true>(options: RESTDataSourceOptions<Ops> & Decoding<Decoded>): RESTDataSource<Ops, Routes, Decoded>;
82
+ /**
83
+ * A datasource class bound to `operations`, whose types it takes from the
84
+ * generated table: `class Bookmarks extends RESTDataSource.for(operations)`.
85
+ * `{ decode: false }` returns the replies as JSON carries them.
86
+ */
87
+ for<Ops extends OperationsShape<Ops>>(operations: OperationTable<Ops>, options: {
88
+ readonly decode: false;
89
+ }): BoundDataSource<Ops, false>;
90
+ for<Ops extends OperationsShape<Ops>>(operations: OperationTable<Ops>, options?: {
91
+ readonly decode?: true;
92
+ }): BoundDataSource<Ops, true>;
93
+ };
94
+ export {};
95
+ //# sourceMappingURL=rest-datasource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest-datasource.d.ts","sourceRoot":"","sources":["../../src/datasource/rest-datasource.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACN,KAAK,KAAK,EAEV,KAAK,iBAAiB,EAItB,KAAK,OAAO,EAEZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,MAAM,sBAAsB,CAAC;AAG9B,KAAK,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAiB,CAAC;AAE7C,MAAM,WAAW,qBAAqB,CAAC,GAAG,CAAE,SAAQ,cAAc;IACjE,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,0EAA0E;IAC1E,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACtB,sFAAsF;IACtF,IAAI,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,MAAM,CAAC,CAAC;CACnD;AAED,sGAAsG;AACtG,KAAK,QAAQ,CAAC,OAAO,SAAS,OAAO,IAAI;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,CAAC,OAAO,SAAS,KAAK,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC;AAEnE,qFAAqF;AACrF,cAAM,UAAU,CACf,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,EACN,OAAO,SAAS,OAAO;IAEvB,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAElD;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO;sBAGjD,MAAM;;gBAMjB,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;IAwCnE;;;OAGG;IACG,IAAI,CAAC,CAAC,SAAS;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,EACvE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CAa9B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,CACzB,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,EACtB,OAAO,SAAS,OAAO,GAAG,IAAI,IAC3B,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzE,4FAA4F;AAC5F,MAAM,MAAM,eAAe,CAC1B,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,OAAO,SAAS,OAAO,GAAG,IAAI,IAC3B,KACH,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,KACnD,cAAc,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;AAEjD,eAAO,MAAM,cAAc,EAA4B;IACtD,KACC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EAChC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,EACtB,OAAO,SAAS,OAAO,GAAG,IAAI,EAE9B,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GACrD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;;OAIG;IACH,GAAG,CAAC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EACnC,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,EAC/B,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;KAAE,GACjC,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,GAAG,CAAC,GAAG,SAAS,eAAe,CAAC,GAAG,CAAC,EACnC,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAA;KAAE,GAClC,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;CAC9B,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `DataSourceError`: every way a call to a service can fail, as one error
3
+ * with a code a GraphQL server reports. It is the package's own, and depends
4
+ * on no exception package of the app's: map it to yours where you catch it.
5
+ */
6
+ import { type ValidationIssue } from '@nxgt/httpyz';
7
+ export type DataSourceErrorCode = 'BAD_REQUEST' | 'UNAUTHENTICATED' | 'FORBIDDEN' | 'NOT_FOUND' | 'INTERNAL_SERVER_ERROR' | 'SERVICE_UNAVAILABLE';
8
+ export interface DataSourceErrorOptions extends ErrorOptions {
9
+ readonly code: DataSourceErrorCode;
10
+ readonly status?: number | undefined;
11
+ readonly data?: unknown;
12
+ readonly issues?: readonly ValidationIssue[] | undefined;
13
+ }
14
+ export declare class DataSourceError extends Error {
15
+ readonly name = "DataSourceError";
16
+ readonly code: DataSourceErrorCode;
17
+ /** The status of the service's reply; none when no reply came back. */
18
+ readonly status: number | undefined;
19
+ /** The body of the service's reply, as it sent it; none when no reply was read. */
20
+ readonly data: unknown;
21
+ /** What a `validate` check refused, in the request or the reply; none otherwise. */
22
+ readonly issues: readonly ValidationIssue[] | undefined;
23
+ /**
24
+ * What a GraphQL server reports with the error: graphql-js reads
25
+ * `extensions` off the error a resolver throws.
26
+ */
27
+ readonly extensions: {
28
+ readonly code: DataSourceErrorCode;
29
+ readonly status: number | undefined;
30
+ };
31
+ constructor(message: string, { code, status, data, issues, ...options }: DataSourceErrorOptions);
32
+ }
33
+ /** The code of a reply's status: any 5xx is the service's failure, any other 4xx a bad request. */
34
+ export declare function codeOf(status: number): DataSourceErrorCode;
35
+ /**
36
+ * `error` as a `DataSourceError`, whatever the client threw. `data` is the
37
+ * body of a reply the error does not carry: an `UndeclaredStatusError`'s,
38
+ * which only a read of its response gives.
39
+ */
40
+ export declare function toDataSourceError(error: unknown, data?: unknown): DataSourceError;
41
+ //# sourceMappingURL=data-source-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-source-error.d.ts","sourceRoot":"","sources":["../../src/errors/data-source-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAMN,KAAK,eAAe,EACpB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,mBAAmB,GAC5B,aAAa,GACb,iBAAiB,GACjB,WAAW,GACX,WAAW,GACX,uBAAuB,GACvB,qBAAqB,CAAC;AAEzB,MAAM,WAAW,sBAAuB,SAAQ,YAAY;IAC3D,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,CAAC;CACzD;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACzC,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,CAAC;IACxD;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;QACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;KACpC,CAAC;gBAGD,OAAO,EAAE,MAAM,EACf,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,sBAAsB;CASnE;AAED,mGAAmG;AACnG,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAM1D;AAYD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,OAAO,GACZ,eAAe,CAwCjB"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from './helpers';
2
- export * from './middlewares';
3
- export * from './rest-datasource';
1
+ export { type BoundDataSource, defaultCache, RESTDataSource, type RESTDataSourceOptions, } from './datasource/rest-datasource';
2
+ export { codeOf, DataSourceError, type DataSourceErrorCode, type DataSourceErrorOptions, toDataSourceError, } from './errors/data-source-error';
3
+ export { relayPaginate } from './pagination/relay';
4
+ export type { Connection, Edge, PageInfo, Paginated } from './pagination/types';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,eAAe,EACpB,YAAY,EACZ,cAAc,EACd,KAAK,qBAAqB,GAC1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACN,MAAM,EACN,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,iBAAiB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -1,38 +1,153 @@
1
- // src/helpers/error-handling.ts
2
- import { CustomException, ErrorCode } from "@nxgt/shared-exceptions";
3
- function errorToException(error, response) {
4
- let status = ErrorCode.BadRequest;
5
- switch (response.status) {
6
- case 401:
7
- status = ErrorCode.Unauthenticated;
8
- break;
9
- case 403:
10
- status = ErrorCode.Forbidden;
11
- break;
12
- case 404:
13
- status = ErrorCode.NotFound;
14
- break;
15
- case 500:
16
- status = ErrorCode.InternalServerError;
17
- break;
18
- default:
19
- status = ErrorCode.BadRequest;
1
+ // src/datasource/rest-datasource.ts
2
+ import {
3
+ createHttpClient,
4
+ isAbortError,
5
+ ok,
6
+ cache as replies,
7
+ UndeclaredStatusError as UndeclaredStatusError2
8
+ } from "@nxgt/httpyz";
9
+ import { METHODS } from "@nxgt/httpyz/integration";
10
+ import {
11
+ createOpenApiClient
12
+ } from "@nxgt/openapi-httpyz";
13
+
14
+ // src/errors/data-source-error.ts
15
+ import {
16
+ NetworkError,
17
+ ReplyStatusError,
18
+ TimeoutError,
19
+ UndeclaredStatusError,
20
+ ValidationError
21
+ } from "@nxgt/httpyz";
22
+
23
+ class DataSourceError extends Error {
24
+ constructor(message, { code, status, data, issues, ...options }) {
25
+ super(message, options);
26
+ this.name = "DataSourceError";
27
+ this.code = code;
28
+ this.status = status;
29
+ this.data = data;
30
+ this.issues = issues;
31
+ this.extensions = { code, status };
32
+ }
33
+ }
34
+ function codeOf(status) {
35
+ if (status === 401)
36
+ return "UNAUTHENTICATED";
37
+ if (status === 403)
38
+ return "FORBIDDEN";
39
+ if (status === 404)
40
+ return "NOT_FOUND";
41
+ if (status >= 500)
42
+ return "INTERNAL_SERVER_ERROR";
43
+ return "BAD_REQUEST";
44
+ }
45
+ var messageOf = (data, fallback) => typeof data === "object" && data !== null && ("message" in data) && typeof data.message === "string" && data.message !== "" ? data.message : fallback;
46
+ function toDataSourceError(error, data) {
47
+ if (error instanceof DataSourceError)
48
+ return error;
49
+ if (error instanceof ReplyStatusError) {
50
+ return new DataSourceError(messageOf(error.data, error.message), {
51
+ code: codeOf(error.status),
52
+ status: error.status,
53
+ data: error.data,
54
+ cause: error
55
+ });
56
+ }
57
+ if (error instanceof UndeclaredStatusError) {
58
+ return new DataSourceError(messageOf(data, error.message), {
59
+ code: codeOf(error.status),
60
+ status: error.status,
61
+ data,
62
+ cause: error
63
+ });
64
+ }
65
+ if (error instanceof NetworkError || error instanceof TimeoutError) {
66
+ return new DataSourceError(error.message, {
67
+ code: "SERVICE_UNAVAILABLE",
68
+ cause: error
69
+ });
70
+ }
71
+ if (error instanceof ValidationError) {
72
+ return new DataSourceError(error.message, {
73
+ code: error.failure.kind === "request" ? "BAD_REQUEST" : "INTERNAL_SERVER_ERROR",
74
+ status: error.failure.status,
75
+ issues: error.failure.issues,
76
+ cause: error
77
+ });
78
+ }
79
+ return new DataSourceError(error instanceof Error ? error.message : "An error occurred", { code: "INTERNAL_SERVER_ERROR", cause: error });
80
+ }
81
+
82
+ // src/datasource/rest-datasource.ts
83
+ var defaultCache = replies();
84
+
85
+ class DataSource {
86
+ static for(operations, fixed = {}) {
87
+ const Base = DataSource;
88
+ return class extends Base {
89
+ constructor(options) {
90
+ super({ ...options, ...fixed, operations });
91
+ }
92
+ };
93
+ }
94
+ constructor(options) {
95
+ const {
96
+ baseUrl,
97
+ operations,
98
+ token,
99
+ cache = defaultCache,
100
+ http = {},
101
+ ...binding
102
+ } = options;
103
+ const client = createHttpClient({
104
+ ...http,
105
+ baseUrl,
106
+ ...token === undefined ? {} : {
107
+ auth: { token: typeof token === "function" ? token : () => token }
108
+ },
109
+ use: [...cache ? [cache] : [], ...http.use ?? []]
110
+ });
111
+ this.api = createOpenApiClient(client, operations, ...[binding]);
112
+ const api = this.api;
113
+ for (const method of METHODS) {
114
+ if (method in api && !(method in this)) {
115
+ Object.defineProperty(this, method, {
116
+ configurable: true,
117
+ writable: true,
118
+ value: api[method]
119
+ });
120
+ }
121
+ }
122
+ }
123
+ async data(call) {
124
+ try {
125
+ return ok(await call);
126
+ } catch (error) {
127
+ if (isAbortError(error))
128
+ throw error;
129
+ throw toDataSourceError(error, error instanceof UndeclaredStatusError2 ? await bodyOf(error.response) : undefined);
130
+ }
20
131
  }
21
- return CustomException.from({
22
- message: error?.message || "An error occurred",
23
- code: status,
24
- options: {},
25
- debugMessage: error?.debugMessage
132
+ }
133
+ var RESTDataSource = DataSource;
134
+ async function bodyOf(response) {
135
+ const text = await response.clone().text().catch(() => {
136
+ return;
26
137
  });
138
+ if (!text)
139
+ return;
140
+ try {
141
+ return JSON.parse(text);
142
+ } catch {
143
+ return text;
144
+ }
27
145
  }
28
- // src/helpers/pagination.utils.ts
29
- function relayPaginate(data) {
30
- const { data: items, metadata } = data;
146
+ // src/pagination/relay.ts
147
+ function relayPaginate(page) {
148
+ const { data: items, metadata } = page;
31
149
  return {
32
- edges: (items ?? []).map((item) => ({
33
- node: item,
34
- cursor: item.id
35
- })),
150
+ edges: (items ?? []).map((item) => ({ node: item, cursor: item.id })),
36
151
  pageInfo: metadata ?? {
37
152
  startCursor: undefined,
38
153
  endCursor: undefined,
@@ -41,71 +156,14 @@ function relayPaginate(data) {
41
156
  }
42
157
  };
43
158
  }
44
- // src/middlewares/auth.middleware.ts
45
- var authMiddleware = (options) => ({
46
- onRequest({ request }) {
47
- if (!options.token) {
48
- return;
49
- }
50
- if (options.shouldUseToken && !options.shouldUseToken(request)) {
51
- return;
52
- }
53
- request.headers.set("Authorization", `Bearer ${typeof options.token === "function" ? options.token() : options.token}`);
54
- }
55
- });
56
- // src/middlewares/cache.middleware.ts
57
- var cache = new Map;
58
- var getCacheKey = (request) => `${request.method}:${request.url}`;
59
- var cacheMiddleware = ({
60
- ttl = 5 * 60 * 1000,
61
- shouldCachePostRequest = (req) => req.url.includes("/search")
62
- } = {}) => ({
63
- onRequest({ request }) {
64
- const key = getCacheKey(request);
65
- const cached = cache.get(key);
66
- if (cached && cached.expiry > Date.now()) {
67
- return cached.value.clone();
68
- }
69
- cache.delete(key);
70
- },
71
- onResponse({ request, response }) {
72
- if (response.ok) {
73
- if (request.method === "POST" && !shouldCachePostRequest?.(request)) {
74
- return;
75
- }
76
- const key = getCacheKey(request);
77
- cache.set(key, {
78
- expiry: Date.now() + ttl,
79
- value: response.clone()
80
- });
81
- }
82
- }
83
- });
84
- // src/rest-datasource.ts
85
- class RESTDataSource {
86
- constructor(options) {
87
- this.client = options.client;
88
- this.client.use(cacheMiddleware(options.cacheOptions));
89
- if (options.authOptions) {
90
- this.client.use(authMiddleware(options.authOptions));
91
- }
92
- this.get = this.client.GET;
93
- this.post = this.client.POST;
94
- this.put = this.client.PUT;
95
- this.patch = this.client.PATCH;
96
- this.delete = this.client.DELETE;
97
- this.head = this.client.HEAD;
98
- this.options = this.client.OPTIONS;
99
- this.trace = this.client.TRACE;
100
- }
101
- }
102
159
  export {
160
+ DataSourceError,
103
161
  RESTDataSource,
104
- authMiddleware,
105
- cacheMiddleware,
106
- errorToException,
107
- relayPaginate
162
+ codeOf,
163
+ defaultCache,
164
+ relayPaginate,
165
+ toDataSourceError
108
166
  };
109
167
 
110
- //# debugId=B0900E11F81FD7B064756E2164756E21
168
+ //# debugId=F26F117CA5680FFB64756E2164756E21
111
169
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/helpers/error-handling.ts", "../src/helpers/pagination.utils.ts", "../src/middlewares/auth.middleware.ts", "../src/middlewares/cache.middleware.ts", "../src/rest-datasource.ts"],
3
+ "sources": ["../src/datasource/rest-datasource.ts", "../src/errors/data-source-error.ts", "../src/pagination/relay.ts"],
4
4
  "sourcesContent": [
5
- "import { CustomException, ErrorCode } from '@nxgt/shared-exceptions';\n\nexport function errorToException(error: any, response: Response) {\n\tlet status = ErrorCode.BadRequest;\n\tswitch (response.status) {\n\t\tcase 401:\n\t\t\tstatus = ErrorCode.Unauthenticated;\n\t\t\tbreak;\n\t\tcase 403:\n\t\t\tstatus = ErrorCode.Forbidden;\n\t\t\tbreak;\n\t\tcase 404:\n\t\t\tstatus = ErrorCode.NotFound;\n\t\t\tbreak;\n\t\tcase 500:\n\t\t\tstatus = ErrorCode.InternalServerError;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tstatus = ErrorCode.BadRequest;\n\t}\n\n\treturn CustomException.from({\n\t\tmessage: error?.message || 'An error occurred',\n\t\tcode: status,\n\t\toptions: {},\n\t\tdebugMessage: error?.debugMessage,\n\t});\n}\n",
6
- "import type { Connection, Paginated } from '../types';\n\nexport function relayPaginate<T extends { id: string }>(\n\tdata: Paginated<T>,\n): Connection<T> {\n\tconst { data: items, metadata } = data;\n\treturn {\n\t\tedges: (items ?? []).map((item) => ({\n\t\t\tnode: item,\n\t\t\tcursor: item.id,\n\t\t})),\n\t\tpageInfo: metadata ?? {\n\t\t\tstartCursor: undefined,\n\t\t\tendCursor: undefined,\n\t\t\thasNextPage: false,\n\t\t\ttotalElements: 0,\n\t\t},\n\t};\n}\n",
7
- "import type { Middleware } from 'openapi-fetch';\n\nexport type AuthMiddlewareOptions = {\n\ttoken?: string | (() => Promise<string>);\n\tshouldUseToken?: (request: Request) => boolean;\n};\n\nexport const authMiddleware = (options: AuthMiddlewareOptions): Middleware => ({\n\tonRequest({ request }) {\n\t\tif (!options.token) {\n\t\t\treturn;\n\t\t}\n\t\tif (options.shouldUseToken && !options.shouldUseToken(request)) {\n\t\t\treturn;\n\t\t}\n\t\trequest.headers.set(\n\t\t\t'Authorization',\n\t\t\t`Bearer ${typeof options.token === 'function' ? options.token() : options.token}`,\n\t\t);\n\t},\n});\n",
8
- "import type { Middleware } from 'openapi-fetch';\n\nconst cache = new Map<string, { expiry: number; value: Response }>();\nconst getCacheKey = (request: Request) => `${request.method}:${request.url}`;\n\nexport type CacheMiddlewareOptions = {\n\tttl?: number;\n\tshouldCachePostRequest?: (req: Request) => boolean;\n};\n\nexport const cacheMiddleware = ({\n\tttl = 5 * 60 * 1000, // default TTL: 5 minutes\n\tshouldCachePostRequest = (req) => req.url.includes('/search'),\n}: CacheMiddlewareOptions = {}): Middleware => ({\n\tonRequest({ request }) {\n\t\tconst key = getCacheKey(request);\n\t\tconst cached = cache.get(key);\n\t\tif (cached && cached.expiry > Date.now()) {\n\t\t\treturn cached.value.clone();\n\t\t}\n\t\tcache.delete(key);\n\t},\n\tonResponse({ request, response }) {\n\t\tif (response.ok) {\n\t\t\tif (request.method === 'POST' && !shouldCachePostRequest?.(request)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst key = getCacheKey(request);\n\t\t\tcache.set(key, {\n\t\t\t\texpiry: Date.now() + ttl,\n\t\t\t\tvalue: response.clone(),\n\t\t\t});\n\t\t}\n\t},\n});\n",
9
- "import type { Client, ClientMethod } from 'openapi-fetch';\nimport {\n\ttype AuthMiddlewareOptions,\n\tauthMiddleware,\n\ttype CacheMiddlewareOptions,\n\tcacheMiddleware,\n} from './middlewares';\nimport type { MediaType } from './types';\n\nexport type RESTDataSourceOptions<\n\tPaths extends {},\n\tMedia extends MediaType = MediaType,\n> = {\n\tclient: Client<Paths, Media>;\n\tcacheOptions?: CacheMiddlewareOptions;\n\tauthOptions?: AuthMiddlewareOptions;\n};\n\nexport class RESTDataSource<\n\tPaths extends {},\n\tMedia extends MediaType = MediaType,\n> {\n\tprotected client: Client<Paths, Media>;\n\n\tget: ClientMethod<Paths, 'get', Media>;\n\tpost: ClientMethod<Paths, 'post', Media>;\n\tput: ClientMethod<Paths, 'put', Media>;\n\tpatch: ClientMethod<Paths, 'patch', Media>;\n\tdelete: ClientMethod<Paths, 'delete', Media>;\n\thead: ClientMethod<Paths, 'head', Media>;\n\toptions: ClientMethod<Paths, 'options', Media>;\n\ttrace: ClientMethod<Paths, 'trace', Media>;\n\n\tconstructor(options: RESTDataSourceOptions<Paths, Media>) {\n\t\tthis.client = options.client;\n\t\tthis.client.use(cacheMiddleware(options.cacheOptions));\n\t\tif (options.authOptions) {\n\t\t\tthis.client.use(authMiddleware(options.authOptions));\n\t\t}\n\t\tthis.get = this.client.GET;\n\t\tthis.post = this.client.POST;\n\t\tthis.put = this.client.PUT;\n\t\tthis.patch = this.client.PATCH;\n\t\tthis.delete = this.client.DELETE;\n\t\tthis.head = this.client.HEAD;\n\t\tthis.options = this.client.OPTIONS;\n\t\tthis.trace = this.client.TRACE;\n\t}\n}\n"
5
+ "/**\n * `RESTDataSource`: a REST service, called from a GraphQL resolver or any\n * server code, through the client `@nxgt/openapi-httpyz` binds to its spec.\n * The caller's token is forwarded, reads are cached, and a call that fails\n * becomes one `DataSourceError`.\n */\nimport {\n\ttype Cache,\n\tcreateHttpClient,\n\ttype HttpClientOptions,\n\tisAbortError,\n\tok,\n\tcache as replies,\n\ttype Success,\n\tUndeclaredStatusError,\n} from '@nxgt/httpyz';\nimport { METHODS } from '@nxgt/httpyz/integration';\nimport {\n\tcreateOpenApiClient,\n\ttype OpenApiArgs,\n\ttype OpenApiClient,\n\ttype OpenApiOptions,\n\ttype OperationsShape,\n\ttype OperationTable,\n\ttype PathMethods,\n\ttype RoutesOf,\n} from '@nxgt/openapi-httpyz';\nimport { toDataSourceError } from '../errors/data-source-error';\n\ntype Token = string | null | undefined;\n\n/**\n * The cache every datasource shares unless it is given its own: a server\n * makes its datasources per request, and each would otherwise start empty.\n * It keeps the reads, `GET`, `HEAD` and `QUERY`, a search by what it searches\n * for. It is keyed by the caller's token, so no caller is answered with\n * another's reply. `defaultCache.clear()` empties it.\n */\nexport const defaultCache: Cache = replies();\n\nexport interface RESTDataSourceOptions<Ops> extends OpenApiOptions {\n\t/** The service's base URL. */\n\tbaseUrl: string;\n\t/** The `operations` table generated from the service's spec. */\n\toperations: OperationTable<Ops>;\n\t/**\n\t * Sent as `Authorization: Bearer <token>`: the caller's, forwarded. A\n\t * function is read, and awaited, before each request. None sends no header.\n\t */\n\ttoken?: Token | (() => Token | Promise<Token>);\n\t/** Default: `defaultCache`, shared by every datasource. `false`: none. */\n\tcache?: Cache | false;\n\t/** What else the core client takes: `headers`, `timeout`, `retry`, `use`, `fetch`… */\n\thttp?: Omit<HttpClientOptions, 'baseUrl' | 'auth'>;\n}\n\n/** `decode`, as the binding takes it: a datasource typed as not decoding must say `decode: false`. */\ntype Decoding<Decoded extends boolean> = {\n\treadonly decode?: Decoded;\n} & (Decoded extends false ? { readonly decode: false } : unknown);\n\n/** The datasource's own members; `RESTDataSource` adds the client's path methods. */\nclass DataSource<\n\tOps extends OperationsShape<Ops>,\n\tRoutes,\n\tDecoded extends boolean,\n> {\n\t/** The client bound to the spec, for `op()`, `stream()` and `group()`. */\n\treadonly api: OpenApiClient<Ops, Routes, Decoded>;\n\n\t/**\n\t * A datasource class bound to `operations`, whose types it takes from the\n\t * generated table: no `ClientOperations` to import. Its constructor takes\n\t * the other options.\n\t */\n\tstatic for(operations: object, fixed: { readonly decode?: boolean } = {}) {\n\t\tconst Base = DataSource as unknown as new (options: object) => object;\n\t\treturn class extends Base {\n\t\t\tconstructor(options: object) {\n\t\t\t\tsuper({ ...options, ...fixed, operations });\n\t\t\t}\n\t\t};\n\t}\n\n\tconstructor(options: RESTDataSourceOptions<Ops> & Decoding<Decoded>) {\n\t\tconst {\n\t\t\tbaseUrl,\n\t\t\toperations,\n\t\t\ttoken,\n\t\t\tcache = defaultCache,\n\t\t\thttp = {},\n\t\t\t...binding\n\t\t} = options;\n\t\tconst client = createHttpClient({\n\t\t\t...http,\n\t\t\tbaseUrl,\n\t\t\t...(token === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tauth: { token: typeof token === 'function' ? token : () => token },\n\t\t\t\t\t}),\n\t\t\t// Inside `auth`, so the cache is keyed by the token it sent.\n\t\t\tuse: [...(cache ? [cache] : []), ...(http.use ?? [])],\n\t\t});\n\t\tthis.api = createOpenApiClient<Ops, Routes, Decoded>(\n\t\t\tclient,\n\t\t\toperations,\n\t\t\t// `Decoding` already holds `decode` to `Decoded`.\n\t\t\t...([binding] as unknown as OpenApiArgs<Decoded>),\n\t\t);\n\t\t// `this.get(...)` for `this.api.get(...)`, for the spec's methods only.\n\t\t// A subclass's own `get()` or `delete()` is already there, and wins.\n\t\tconst api = this.api as unknown as Record<string, unknown>;\n\t\tfor (const method of METHODS) {\n\t\t\tif (method in api && !(method in this)) {\n\t\t\t\tObject.defineProperty(this, method, {\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\twritable: true,\n\t\t\t\t\tvalue: api[method],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * The data of a call's 2xx reply. Any other reply, or none, throws a\n\t * `DataSourceError`; an aborted call throws its abort, as it is.\n\t */\n\tasync data<R extends { readonly status: number; readonly data: unknown }>(\n\t\tcall: Promise<R>,\n\t): Promise<Success<R>['data']> {\n\t\ttry {\n\t\t\treturn ok(await call);\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) throw error;\n\t\t\tthrow toDataSourceError(\n\t\t\t\terror,\n\t\t\t\terror instanceof UndeclaredStatusError\n\t\t\t\t\t? await bodyOf(error.response)\n\t\t\t\t\t: undefined,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * A datasource for a service, typed by the `ClientOperations` generated from\n * its spec. It has the client's `get`, `post`… for each method the spec has\n * an operation for. Extend it with the calls a resolver makes:\n *\n * ```ts\n * class Bookmarks extends RESTDataSource.for(operations) {\n * \tbookmark(id: string) {\n * \t\treturn this.data(this.get('/bookmarks/{id}', { param: { id } }));\n * \t}\n * }\n * new Bookmarks({ baseUrl, token: () => context.token });\n * ```\n */\nexport type RESTDataSource<\n\tOps extends OperationsShape<Ops>,\n\tRoutes = RoutesOf<Ops>,\n\tDecoded extends boolean = true,\n> = DataSource<Ops, Routes, Decoded> & PathMethods<Ops, Routes, Decoded>;\n\n/** What `RESTDataSource.for(operations)` returns: a datasource class bound to the table. */\nexport type BoundDataSource<\n\tOps extends OperationsShape<Ops>,\n\tDecoded extends boolean = true,\n> = new (\n\toptions: Omit<RESTDataSourceOptions<Ops>, 'operations'>,\n) => RESTDataSource<Ops, RoutesOf<Ops>, Decoded>;\n\nexport const RESTDataSource = DataSource as unknown as {\n\tnew <\n\t\tOps extends OperationsShape<Ops>,\n\t\tRoutes = RoutesOf<Ops>,\n\t\tDecoded extends boolean = true,\n\t>(\n\t\toptions: RESTDataSourceOptions<Ops> & Decoding<Decoded>,\n\t): RESTDataSource<Ops, Routes, Decoded>;\n\t/**\n\t * A datasource class bound to `operations`, whose types it takes from the\n\t * generated table: `class Bookmarks extends RESTDataSource.for(operations)`.\n\t * `{ decode: false }` returns the replies as JSON carries them.\n\t */\n\tfor<Ops extends OperationsShape<Ops>>(\n\t\toperations: OperationTable<Ops>,\n\t\toptions: { readonly decode: false },\n\t): BoundDataSource<Ops, false>;\n\tfor<Ops extends OperationsShape<Ops>>(\n\t\toperations: OperationTable<Ops>,\n\t\toptions?: { readonly decode?: true },\n\t): BoundDataSource<Ops, true>;\n};\n\n/** A reply's body: JSON when it parses, its text otherwise, nothing when it cannot be read. */\nasync function bodyOf(response: Response): Promise<unknown> {\n\tconst text = await response\n\t\t.clone()\n\t\t.text()\n\t\t.catch(() => undefined);\n\tif (!text) return undefined;\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch {\n\t\treturn text;\n\t}\n}\n",
6
+ "/**\n * `DataSourceError`: every way a call to a service can fail, as one error\n * with a code a GraphQL server reports. It is the package's own, and depends\n * on no exception package of the app's: map it to yours where you catch it.\n */\nimport {\n\tNetworkError,\n\tReplyStatusError,\n\tTimeoutError,\n\tUndeclaredStatusError,\n\tValidationError,\n\ttype ValidationIssue,\n} from '@nxgt/httpyz';\n\nexport type DataSourceErrorCode =\n\t| 'BAD_REQUEST'\n\t| 'UNAUTHENTICATED'\n\t| 'FORBIDDEN'\n\t| 'NOT_FOUND'\n\t| 'INTERNAL_SERVER_ERROR'\n\t| 'SERVICE_UNAVAILABLE';\n\nexport interface DataSourceErrorOptions extends ErrorOptions {\n\treadonly code: DataSourceErrorCode;\n\treadonly status?: number | undefined;\n\treadonly data?: unknown;\n\treadonly issues?: readonly ValidationIssue[] | undefined;\n}\n\nexport class DataSourceError extends Error {\n\toverride readonly name = 'DataSourceError';\n\treadonly code: DataSourceErrorCode;\n\t/** The status of the service's reply; none when no reply came back. */\n\treadonly status: number | undefined;\n\t/** The body of the service's reply, as it sent it; none when no reply was read. */\n\treadonly data: unknown;\n\t/** What a `validate` check refused, in the request or the reply; none otherwise. */\n\treadonly issues: readonly ValidationIssue[] | undefined;\n\t/**\n\t * What a GraphQL server reports with the error: graphql-js reads\n\t * `extensions` off the error a resolver throws.\n\t */\n\treadonly extensions: {\n\t\treadonly code: DataSourceErrorCode;\n\t\treadonly status: number | undefined;\n\t};\n\n\tconstructor(\n\t\tmessage: string,\n\t\t{ code, status, data, issues, ...options }: DataSourceErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.status = status;\n\t\tthis.data = data;\n\t\tthis.issues = issues;\n\t\tthis.extensions = { code, status };\n\t}\n}\n\n/** The code of a reply's status: any 5xx is the service's failure, any other 4xx a bad request. */\nexport function codeOf(status: number): DataSourceErrorCode {\n\tif (status === 401) return 'UNAUTHENTICATED';\n\tif (status === 403) return 'FORBIDDEN';\n\tif (status === 404) return 'NOT_FOUND';\n\tif (status >= 500) return 'INTERNAL_SERVER_ERROR';\n\treturn 'BAD_REQUEST';\n}\n\n/** The `message` of an error body, as the services write it, or `fallback`. */\nconst messageOf = (data: unknown, fallback: string): string =>\n\ttypeof data === 'object' &&\n\tdata !== null &&\n\t'message' in data &&\n\ttypeof data.message === 'string' &&\n\tdata.message !== ''\n\t\t? data.message\n\t\t: fallback;\n\n/**\n * `error` as a `DataSourceError`, whatever the client threw. `data` is the\n * body of a reply the error does not carry: an `UndeclaredStatusError`'s,\n * which only a read of its response gives.\n */\nexport function toDataSourceError(\n\terror: unknown,\n\tdata?: unknown,\n): DataSourceError {\n\tif (error instanceof DataSourceError) return error;\n\tif (error instanceof ReplyStatusError) {\n\t\treturn new DataSourceError(messageOf(error.data, error.message), {\n\t\t\tcode: codeOf(error.status),\n\t\t\tstatus: error.status,\n\t\t\tdata: error.data,\n\t\t\tcause: error,\n\t\t});\n\t}\n\tif (error instanceof UndeclaredStatusError) {\n\t\treturn new DataSourceError(messageOf(data, error.message), {\n\t\t\tcode: codeOf(error.status),\n\t\t\tstatus: error.status,\n\t\t\tdata,\n\t\t\tcause: error,\n\t\t});\n\t}\n\tif (error instanceof NetworkError || error instanceof TimeoutError) {\n\t\treturn new DataSourceError(error.message, {\n\t\t\tcode: 'SERVICE_UNAVAILABLE',\n\t\t\tcause: error,\n\t\t});\n\t}\n\tif (error instanceof ValidationError) {\n\t\t// A request the spec refuses is the caller's fault; a reply it refuses, the service's.\n\t\treturn new DataSourceError(error.message, {\n\t\t\tcode:\n\t\t\t\terror.failure.kind === 'request'\n\t\t\t\t\t? 'BAD_REQUEST'\n\t\t\t\t\t: 'INTERNAL_SERVER_ERROR',\n\t\t\tstatus: error.failure.status,\n\t\t\tissues: error.failure.issues,\n\t\t\tcause: error,\n\t\t});\n\t}\n\treturn new DataSourceError(\n\t\terror instanceof Error ? error.message : 'An error occurred',\n\t\t{ code: 'INTERNAL_SERVER_ERROR', cause: error },\n\t);\n}\n",
7
+ "import type { Connection, Paginated } from './types';\n\n/**\n * A service's `{ data, metadata }` page as a Relay connection: each item an\n * edge, its `id` the cursor. Without `metadata`, its page info is an empty\n * last page's, even when it has items.\n */\nexport function relayPaginate<T extends { id: string }>(\n\tpage: Paginated<T>,\n): Connection<T> {\n\tconst { data: items, metadata } = page;\n\treturn {\n\t\tedges: (items ?? []).map((item) => ({ node: item, cursor: item.id })),\n\t\tpageInfo: metadata ?? {\n\t\t\tstartCursor: undefined,\n\t\t\tendCursor: undefined,\n\t\t\thasNextPage: false,\n\t\t\ttotalElements: 0,\n\t\t},\n\t};\n}\n"
10
8
  ],
11
- "mappings": "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",
12
- "debugId": "B0900E11F81FD7B064756E2164756E21",
9
+ "mappings": ";AAMA;AAAA;AAAA;AAAA;AAAA,WAMC;AAAA,2BAEA;AAAA;AAED;AACA;AAAA;AAAA;;;ACZA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,MAAM,wBAAwB,MAAM;AAAA,EAkB1C,WAAW,CACV,WACE,MAAM,QAAQ,MAAM,WAAW,WAChC;AAAA,IACD,MAAM,SAAS,OAAO;AAAA,IArBL,YAAO;AAAA,IAsBxB,KAAK,OAAO;AAAA,IACZ,KAAK,SAAS;AAAA,IACd,KAAK,OAAO;AAAA,IACZ,KAAK,SAAS;AAAA,IACd,KAAK,aAAa,EAAE,MAAM,OAAO;AAAA;AAEnC;AAGO,SAAS,MAAM,CAAC,QAAqC;AAAA,EAC3D,IAAI,WAAW;AAAA,IAAK,OAAO;AAAA,EAC3B,IAAI,WAAW;AAAA,IAAK,OAAO;AAAA,EAC3B,IAAI,WAAW;AAAA,IAAK,OAAO;AAAA,EAC3B,IAAI,UAAU;AAAA,IAAK,OAAO;AAAA,EAC1B,OAAO;AAAA;AAIR,IAAM,YAAY,CAAC,MAAe,aACjC,OAAO,SAAS,YAChB,SAAS,SACT,aAAa,SACb,OAAO,KAAK,YAAY,YACxB,KAAK,YAAY,KACd,KAAK,UACL;AAOG,SAAS,iBAAiB,CAChC,OACA,MACkB;AAAA,EAClB,IAAI,iBAAiB;AAAA,IAAiB,OAAO;AAAA,EAC7C,IAAI,iBAAiB,kBAAkB;AAAA,IACtC,OAAO,IAAI,gBAAgB,UAAU,MAAM,MAAM,MAAM,OAAO,GAAG;AAAA,MAChE,MAAM,OAAO,MAAM,MAAM;AAAA,MACzB,QAAQ,MAAM;AAAA,MACd,MAAM,MAAM;AAAA,MACZ,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA,EACA,IAAI,iBAAiB,uBAAuB;AAAA,IAC3C,OAAO,IAAI,gBAAgB,UAAU,MAAM,MAAM,OAAO,GAAG;AAAA,MAC1D,MAAM,OAAO,MAAM,MAAM;AAAA,MACzB,QAAQ,MAAM;AAAA,MACd;AAAA,MACA,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA,EACA,IAAI,iBAAiB,gBAAgB,iBAAiB,cAAc;AAAA,IACnE,OAAO,IAAI,gBAAgB,MAAM,SAAS;AAAA,MACzC,MAAM;AAAA,MACN,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA,EACA,IAAI,iBAAiB,iBAAiB;AAAA,IAErC,OAAO,IAAI,gBAAgB,MAAM,SAAS;AAAA,MACzC,MACC,MAAM,QAAQ,SAAS,YACpB,gBACA;AAAA,MACJ,QAAQ,MAAM,QAAQ;AAAA,MACtB,QAAQ,MAAM,QAAQ;AAAA,MACtB,OAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA,EACA,OAAO,IAAI,gBACV,iBAAiB,QAAQ,MAAM,UAAU,qBACzC,EAAE,MAAM,yBAAyB,OAAO,MAAM,CAC/C;AAAA;;;ADxFM,IAAM,eAAsB,QAAQ;AAAA;AAwB3C,MAAM,WAIJ;AAAA,SASM,GAAG,CAAC,YAAoB,QAAuC,CAAC,GAAG;AAAA,IACzE,MAAM,OAAO;AAAA,IACb,OAAO,cAAc,KAAK;AAAA,MACzB,WAAW,CAAC,SAAiB;AAAA,QAC5B,MAAM,KAAK,YAAY,OAAO,WAAW,CAAC;AAAA;AAAA,IAE5C;AAAA;AAAA,EAGD,WAAW,CAAC,SAAyD;AAAA,IACpE;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,CAAC;AAAA,SACL;AAAA,QACA;AAAA,IACJ,MAAM,SAAS,iBAAiB;AAAA,SAC5B;AAAA,MACH;AAAA,SACI,UAAU,YACX,CAAC,IACD;AAAA,QACA,MAAM,EAAE,OAAO,OAAO,UAAU,aAAa,QAAQ,MAAM,MAAM;AAAA,MAClE;AAAA,MAEF,KAAK,CAAC,GAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,GAAI,GAAI,KAAK,OAAO,CAAC,CAAE;AAAA,IACrD,CAAC;AAAA,IACD,KAAK,MAAM,oBACV,QACA,YAEA,GAAI,CAAC,OAAO,CACb;AAAA,IAGA,MAAM,MAAM,KAAK;AAAA,IACjB,WAAW,UAAU,SAAS;AAAA,MAC7B,IAAI,UAAU,OAAO,EAAE,UAAU,OAAO;AAAA,QACvC,OAAO,eAAe,MAAM,QAAQ;AAAA,UACnC,cAAc;AAAA,UACd,UAAU;AAAA,UACV,OAAO,IAAI;AAAA,QACZ,CAAC;AAAA,MACF;AAAA,IACD;AAAA;AAAA,OAOK,KAAmE,CACxE,MAC8B;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,GAAG,MAAM,IAAI;AAAA,MACnB,OAAO,OAAO;AAAA,MACf,IAAI,aAAa,KAAK;AAAA,QAAG,MAAM;AAAA,MAC/B,MAAM,kBACL,OACA,iBAAiB,yBACd,MAAM,OAAO,MAAM,QAAQ,IAC3B,SACJ;AAAA;AAAA;AAGH;AA8BO,IAAM,iBAAiB;AAwB9B,eAAe,MAAM,CAAC,UAAsC;AAAA,EAC3D,MAAM,OAAO,MAAM,SACjB,MAAM,EACN,KAAK,EACL,MAAM,MAAG;AAAA,IAAG;AAAA,GAAS;AAAA,EACvB,IAAI,CAAC;AAAA,IAAM;AAAA,EACX,IAAI;AAAA,IACH,OAAO,KAAK,MAAM,IAAI;AAAA,IACrB,MAAM;AAAA,IACP,OAAO;AAAA;AAAA;;AEvMF,SAAS,aAAuC,CACtD,MACgB;AAAA,EAChB,QAAQ,MAAM,OAAO,aAAa;AAAA,EAClC,OAAO;AAAA,IACN,QAAQ,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,MAAM,QAAQ,KAAK,GAAG,EAAE;AAAA,IACpE,UAAU,YAAY;AAAA,MACrB,aAAa;AAAA,MACb,WAAW;AAAA,MACX,aAAa;AAAA,MACb,eAAe;AAAA,IAChB;AAAA,EACD;AAAA;",
10
+ "debugId": "F26F117CA5680FFB64756E2164756E21",
13
11
  "names": []
14
12
  }
@@ -0,0 +1,10 @@
1
+ import type { Connection, Paginated } from './types';
2
+ /**
3
+ * A service's `{ data, metadata }` page as a Relay connection: each item an
4
+ * edge, its `id` the cursor. Without `metadata`, its page info is an empty
5
+ * last page's, even when it has items.
6
+ */
7
+ export declare function relayPaginate<T extends {
8
+ id: string;
9
+ }>(page: Paginated<T>): Connection<T>;
10
+ //# sourceMappingURL=relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/pagination/relay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACrD,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAChB,UAAU,CAAC,CAAC,CAAC,CAWf"}
@@ -0,0 +1,25 @@
1
+ /** A service's page, and the Relay connection a GraphQL schema serves it as. */
2
+ export interface PageInfo {
3
+ /** The cursor of the first item in the result set. */
4
+ startCursor?: string;
5
+ /** The cursor of the last item in the result set. */
6
+ endCursor?: string;
7
+ /** Whether there are more items after the current page. */
8
+ hasNextPage: boolean;
9
+ /** The number of items across all pages. */
10
+ totalElements: number;
11
+ }
12
+ /** A page as the services send it. */
13
+ export type Paginated<T> = {
14
+ data?: T[];
15
+ metadata?: PageInfo;
16
+ };
17
+ export type Edge<T> = {
18
+ node: T;
19
+ cursor: string;
20
+ };
21
+ export type Connection<T> = {
22
+ edges: Edge<T>[];
23
+ pageInfo: PageInfo;
24
+ };
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pagination/types.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,WAAW,QAAQ;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IAC1B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC3B,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACnB,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@nxgt/datasource-rest",
3
- "version": "1.0.3",
4
- "license": "UNLICENSED",
3
+ "version": "3.0.0",
4
+ "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
10
  "README.md",
11
- "package.json"
11
+ "package.json",
12
+ "LICENSE"
12
13
  ],
13
14
  "exports": {
14
15
  ".": {
@@ -20,7 +21,7 @@
20
21
  },
21
22
  "repository": {
22
23
  "type": "git",
23
- "url": "git+https://github.com/softistx/nxgt-core.git",
24
+ "url": "git+https://github.com/softistx/nxgt-http.git",
24
25
  "directory": "packages/datasource-rest"
25
26
  },
26
27
  "publishConfig": {
@@ -29,24 +30,25 @@
29
30
  },
30
31
  "scripts": {
31
32
  "build": "bun run ../../build.ts",
32
- "typecheck": "tsc --noEmit"
33
+ "fixtures": "bun run test/generate.ts",
34
+ "test": "bun run fixtures && bun test src",
35
+ "typecheck": "bun run fixtures && tsc --noEmit"
33
36
  },
34
37
  "nxgt": {
35
38
  "entrypoints": [
36
39
  "src/index.ts"
37
40
  ]
38
41
  },
39
- "dependencies": {
40
- "lodash": "^4.18.1",
41
- "@nxgt/shared": "^1.0.2",
42
- "@nxgt/shared-exceptions": "^1.0.2",
43
- "openapi-fetch": "^0.17.0"
44
- },
45
42
  "devDependencies": {
43
+ "@nxgt/httpyz": "^0.1.0",
44
+ "@nxgt/openapi-codegen": "^0.2.0",
45
+ "@nxgt/openapi-httpyz": "^0.1.0",
46
46
  "@types/bun": "^1.4.0",
47
- "@types/lodash": "^4.17.24"
47
+ "zod": "^4.5.4"
48
48
  },
49
49
  "peerDependencies": {
50
+ "@nxgt/httpyz": "^0.1.0",
51
+ "@nxgt/openapi-httpyz": "^0.1.0",
50
52
  "typescript": "^6.0.3"
51
53
  }
52
54
  }
@@ -1,3 +0,0 @@
1
- import { CustomException } from '@nxgt/shared-exceptions';
2
- export declare function errorToException(error: any, response: Response): CustomException<any>;
3
- //# sourceMappingURL=error-handling.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error-handling.d.ts","sourceRoot":"","sources":["../../src/helpers/error-handling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,yBAAyB,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,wBAyB9D"}
@@ -1,3 +0,0 @@
1
- export * from './error-handling';
2
- export * from './pagination.utils';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC"}
@@ -1,5 +0,0 @@
1
- import type { Connection, Paginated } from '../types';
2
- export declare function relayPaginate<T extends {
3
- id: string;
4
- }>(data: Paginated<T>): Connection<T>;
5
- //# sourceMappingURL=pagination.utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pagination.utils.d.ts","sourceRoot":"","sources":["../../src/helpers/pagination.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEtD,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACrD,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAChB,UAAU,CAAC,CAAC,CAAC,CAcf"}
@@ -1,7 +0,0 @@
1
- import type { Middleware } from 'openapi-fetch';
2
- export type AuthMiddlewareOptions = {
3
- token?: string | (() => Promise<string>);
4
- shouldUseToken?: (request: Request) => boolean;
5
- };
6
- export declare const authMiddleware: (options: AuthMiddlewareOptions) => Middleware;
7
- //# sourceMappingURL=auth.middleware.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/auth.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,qBAAqB,KAAG,UAa9D,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { Middleware } from 'openapi-fetch';
2
- export type CacheMiddlewareOptions = {
3
- ttl?: number;
4
- shouldCachePostRequest?: (req: Request) => boolean;
5
- };
6
- export declare const cacheMiddleware: ({ ttl, shouldCachePostRequest, }?: CacheMiddlewareOptions) => Middleware;
7
- //# sourceMappingURL=cache.middleware.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.middleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/cache.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKhD,MAAM,MAAM,sBAAsB,GAAG;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CACnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,mCAG7B,sBAA2B,KAAG,UAqB/B,CAAC"}
@@ -1,3 +0,0 @@
1
- export * from './auth.middleware';
2
- export * from './cache.middleware';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
@@ -1,21 +0,0 @@
1
- import type { Client, ClientMethod } from 'openapi-fetch';
2
- import { type AuthMiddlewareOptions, type CacheMiddlewareOptions } from './middlewares';
3
- import type { MediaType } from './types';
4
- export type RESTDataSourceOptions<Paths extends {}, Media extends MediaType = MediaType> = {
5
- client: Client<Paths, Media>;
6
- cacheOptions?: CacheMiddlewareOptions;
7
- authOptions?: AuthMiddlewareOptions;
8
- };
9
- export declare class RESTDataSource<Paths extends {}, Media extends MediaType = MediaType> {
10
- protected client: Client<Paths, Media>;
11
- get: ClientMethod<Paths, 'get', Media>;
12
- post: ClientMethod<Paths, 'post', Media>;
13
- put: ClientMethod<Paths, 'put', Media>;
14
- patch: ClientMethod<Paths, 'patch', Media>;
15
- delete: ClientMethod<Paths, 'delete', Media>;
16
- head: ClientMethod<Paths, 'head', Media>;
17
- options: ClientMethod<Paths, 'options', Media>;
18
- trace: ClientMethod<Paths, 'trace', Media>;
19
- constructor(options: RESTDataSourceOptions<Paths, Media>);
20
- }
21
- //# sourceMappingURL=rest-datasource.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rest-datasource.d.ts","sourceRoot":"","sources":["../src/rest-datasource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EACN,KAAK,qBAAqB,EAE1B,KAAK,sBAAsB,EAE3B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,qBAAqB,CAChC,KAAK,SAAS,EAAE,EAChB,KAAK,SAAS,SAAS,GAAG,SAAS,IAChC;IACH,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACpC,CAAC;AAEF,qBAAa,cAAc,CAC1B,KAAK,SAAS,EAAE,EAChB,KAAK,SAAS,SAAS,GAAG,SAAS;IAEnC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEvC,GAAG,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACzC,GAAG,EAAE,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACvC,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACzC,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/C,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;gBAE/B,OAAO,EAAE,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC;CAexD"}
package/dist/types.d.ts DELETED
@@ -1,39 +0,0 @@
1
- export type MediaType = `${string}/${string}`;
2
- export type UpperHttpMethod = 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH' | 'TRACE';
3
- export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace';
4
- export type ErrorResponse = {
5
- status?: number;
6
- message?: string;
7
- debugMessage?: string;
8
- };
9
- export interface PageInfo {
10
- /**
11
- * The cursor of the first item in the result set.
12
- */
13
- startCursor?: string;
14
- /**
15
- * The cursor of the last item in the result set.
16
- */
17
- endCursor?: string;
18
- /**
19
- * Indicates whether there are more items after the current page.
20
- */
21
- hasNextPage: boolean;
22
- /**
23
- * The total number of elements across all pages.
24
- */
25
- totalElements: number;
26
- }
27
- export type Paginated<T> = {
28
- data?: T[];
29
- metadata?: PageInfo;
30
- };
31
- export type Edge<T> = {
32
- node: T;
33
- cursor: string;
34
- };
35
- export type Connection<T> = {
36
- edges: Edge<T>[];
37
- pageInfo: PageInfo;
38
- };
39
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAE9C,MAAM,MAAM,eAAe,GACxB,KAAK,GACL,KAAK,GACL,MAAM,GACN,QAAQ,GACR,SAAS,GACT,MAAM,GACN,OAAO,GACP,OAAO,CAAC;AAEX,MAAM,MAAM,UAAU,GACnB,KAAK,GACL,KAAK,GACL,MAAM,GACN,QAAQ,GACR,SAAS,GACT,MAAM,GACN,OAAO,GACP,OAAO,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,QAAQ;IACxB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IAC1B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC3B,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACnB,CAAC"}