@panneau/data 5.0.0-alpha.2 → 5.0.0-alpha.3
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 +28 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/laravel/index.d.ts +34 -1
- package/dist/laravel/index.d.ts.map +1 -1
- package/dist/laravel/index.js +43 -10
- package/dist/laravel/index.js.map +1 -1
- package/dist/problem.d.ts +16 -0
- package/dist/problem.d.ts.map +1 -0
- package/dist/problem.js +32 -0
- package/dist/problem.js.map +1 -0
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +2 -15
- package/dist/rest.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ try {
|
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
| Export | Content
|
|
22
|
-
| ----------------------- |
|
|
23
|
-
| `@panneau/data` | The `DataAdapter` interface, `createRestAdapter()`,
|
|
24
|
-
| `@panneau/data/laravel` | `createLaravelAdapter()`: the REST adapter with credentials, the `X-XSRF-TOKEN` header from Laravel's cookie, and `419` as unauthorized
|
|
25
|
-
| `@panneau/data/query` | `DataAdapterProvider` and the TanStack Query hooks: `useDefinition`, `useResourceList`, `useResourceItem`, `useCreateItem`, `useUpdateItem`, `useDeleteItem`, `useRunAction`, `useActionRun`
|
|
21
|
+
| Export | Content |
|
|
22
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `@panneau/data` | The `DataAdapter` interface, `createRestAdapter()`, the errors (`ApiError`, `ValidationError`, `ConflictError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`), `createApiError()` and `readProblem()` |
|
|
24
|
+
| `@panneau/data/laravel` | `createLaravelAdapter()`: the REST adapter with credentials, the `X-XSRF-TOKEN` header from Laravel's cookie, and `419` as unauthorized; `getLaravelHeaders()` and `createLaravelError()` for an app's own endpoints |
|
|
25
|
+
| `@panneau/data/query` | `DataAdapterProvider` and the TanStack Query hooks: `useDefinition`, `useResourceList`, `useResourceItem`, `useCreateItem`, `useUpdateItem`, `useDeleteItem`, `useRunAction`, `useActionRun` |
|
|
26
26
|
|
|
27
27
|
## Adapters
|
|
28
28
|
|
|
@@ -36,6 +36,29 @@ A `DataAdapter` is everything the engine and the app need from a backend: the de
|
|
|
36
36
|
- **`createLaravelAdapter()`** is the preset for `laravel-panneau` with session authentication. Laravel specifics stay there, not in the protocol.
|
|
37
37
|
- **A custom adapter** implements `DataAdapter` over another API. A product that only needs a few resources can throw from the methods it doesn't support.
|
|
38
38
|
|
|
39
|
+
### An app's own endpoints
|
|
40
|
+
|
|
41
|
+
An app that uses Panneau usually has its own JSON endpoints too. The pieces of the adapters are exported, so that its client gets the same errors, and can pass a `ValidationError` to a Panneau form or handle `UnauthorizedError` in one place:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { readProblem } from '@panneau/data';
|
|
45
|
+
import { createLaravelError, getLaravelHeaders } from '@panneau/data/laravel';
|
|
46
|
+
|
|
47
|
+
const response = await fetch('/api/subscriptions', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
credentials: 'include',
|
|
50
|
+
headers: getLaravelHeaders({ headers: { 'Content-Type': 'application/json' } }),
|
|
51
|
+
body: JSON.stringify(values),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw createLaravelError(await readProblem(response));
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `readProblem(response)` reads an `application/problem+json` body, or builds a problem from Laravel's `{ message, errors }` body or from the status. It reads a clone, so the response stays readable.
|
|
59
|
+
- `createApiError(problem)` maps a problem to its typed error by status; `createLaravelError(problem)` also treats `419` (an expired CSRF token or session) as `UnauthorizedError`.
|
|
60
|
+
- `getLaravelHeaders({ headers, xsrfCookie, xsrfHeader, cookies })` adds `X-Requested-With` and the CSRF token from Laravel's cookie.
|
|
61
|
+
|
|
39
62
|
## Query hooks
|
|
40
63
|
|
|
41
64
|
The hooks need TanStack Query v5 and React (optional peer dependencies, only for this entry point), with a `QueryClientProvider` and a `DataAdapterProvider` above them.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { ActionResponse, DataAdapter, LoginCredentials, MediaQuery, RequestOptions, UpdateOptions, Values, } from './adapter';
|
|
2
2
|
export { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, isApiError, } from './errors';
|
|
3
|
+
export { readProblem } from './problem';
|
|
3
4
|
export { type RestAdapterOptions, createRestAdapter } from './rest';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,UAAU,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,UAAU,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,KAAK,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, isApiError } from "./errors.js";
|
|
2
|
+
import { readProblem } from "./problem.js";
|
|
2
3
|
import { createRestAdapter } from "./rest.js";
|
|
3
|
-
export { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, createRestAdapter, isApiError };
|
|
4
|
+
export { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, createRestAdapter, isApiError, readProblem };
|
package/dist/laravel/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { Problem } from '@panneau/protocol';
|
|
1
2
|
import type { DataAdapter } from '../adapter';
|
|
3
|
+
import { type ApiError } from '../errors';
|
|
2
4
|
import { type RestAdapterOptions } from '../rest';
|
|
3
|
-
|
|
5
|
+
/** How to send Laravel's CSRF token. */
|
|
6
|
+
export interface LaravelSessionOptions {
|
|
4
7
|
/** Name of the cookie that holds the CSRF token. Defaults to Laravel's `XSRF-TOKEN`. */
|
|
5
8
|
xsrfCookie?: string;
|
|
6
9
|
/** Header that sends the CSRF token. Defaults to `X-XSRF-TOKEN`. */
|
|
@@ -8,6 +11,36 @@ export interface LaravelAdapterOptions extends RestAdapterOptions {
|
|
|
8
11
|
/** Reads the cookies. Defaults to `document.cookie`, or no cookies outside a browser. */
|
|
9
12
|
cookies?: () => string;
|
|
10
13
|
}
|
|
14
|
+
export interface LaravelAdapterOptions extends RestAdapterOptions, LaravelSessionOptions {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The headers of a request to a Laravel app with session authentication: `X-Requested-With`, so
|
|
18
|
+
* that Laravel answers errors as JSON, and the CSRF token from the `XSRF-TOKEN` cookie. Use them,
|
|
19
|
+
* with `credentials: 'include'`, for an app's own endpoints next to Panneau's.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* fetch('/api/subscriptions', {
|
|
23
|
+
* method: 'POST',
|
|
24
|
+
* credentials: 'include',
|
|
25
|
+
* headers: getLaravelHeaders({ headers: { 'Content-Type': 'application/json' } }),
|
|
26
|
+
* body: JSON.stringify(values),
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function getLaravelHeaders({ headers, xsrfCookie, xsrfHeader, cookies, }?: LaravelSessionOptions & {
|
|
31
|
+
headers?: HeadersInit;
|
|
32
|
+
}): Headers;
|
|
33
|
+
/**
|
|
34
|
+
* The typed error of a failed response of a Laravel app: `createApiError()`, with `419` (an
|
|
35
|
+
* expired CSRF token or session) as `UnauthorizedError`, like `401`.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* if (!response.ok) {
|
|
39
|
+
* throw createLaravelError(await readProblem(response));
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function createLaravelError(problem: Problem): ApiError;
|
|
11
44
|
/**
|
|
12
45
|
* The REST adapter set up for `laravel-panneau` with Laravel's session authentication: it sends
|
|
13
46
|
* cookies, adds the CSRF token from the `XSRF-TOKEN` cookie to every request, and treats `419`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/laravel/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/laravel/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,SAAS,CAAC;AAErE,wCAAwC;AACxC,MAAM,WAAW,qBAAqB;IAClC,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB,EAAE,qBAAqB;CAAG;AAgB3F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,OAAO,EACP,UAAyB,EACzB,UAA2B,EAC3B,OAAwE,GAC3E,GAAE,qBAAqB,GAAG;IAAE,OAAO,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,OAAO,CAQlE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,UAAyB,EACzB,UAA2B,EAC3B,OAAwE,EACxE,OAAO,EACP,WAAuB,EACvB,WAAW,EACX,GAAG,OAAO,EACb,EAAE,qBAAqB,GAAG,WAAW,CAerC"}
|
package/dist/laravel/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnauthorizedError } from "../errors.js";
|
|
1
|
+
import { UnauthorizedError, createApiError } from "../errors.js";
|
|
2
2
|
import { createRestAdapter } from "../rest.js";
|
|
3
3
|
//#region src/laravel/index.ts
|
|
4
4
|
function readCookie(cookies, name) {
|
|
@@ -13,6 +13,40 @@ function readCookie(cookies, name) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
+
* The headers of a request to a Laravel app with session authentication: `X-Requested-With`, so
|
|
17
|
+
* that Laravel answers errors as JSON, and the CSRF token from the `XSRF-TOKEN` cookie. Use them,
|
|
18
|
+
* with `credentials: 'include'`, for an app's own endpoints next to Panneau's.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* fetch('/api/subscriptions', {
|
|
22
|
+
* method: 'POST',
|
|
23
|
+
* credentials: 'include',
|
|
24
|
+
* headers: getLaravelHeaders({ headers: { 'Content-Type': 'application/json' } }),
|
|
25
|
+
* body: JSON.stringify(values),
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function getLaravelHeaders({ headers, xsrfCookie = "XSRF-TOKEN", xsrfHeader = "X-XSRF-TOKEN", cookies = () => typeof document === "undefined" ? "" : document.cookie } = {}) {
|
|
30
|
+
const result = new Headers(headers);
|
|
31
|
+
result.set("X-Requested-With", "XMLHttpRequest");
|
|
32
|
+
const token = readCookie(cookies(), xsrfCookie);
|
|
33
|
+
if (token !== null) result.set(xsrfHeader, token);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The typed error of a failed response of a Laravel app: `createApiError()`, with `419` (an
|
|
38
|
+
* expired CSRF token or session) as `UnauthorizedError`, like `401`.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* if (!response.ok) {
|
|
42
|
+
* throw createLaravelError(await readProblem(response));
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function createLaravelError(problem) {
|
|
47
|
+
return problem.status === 419 ? new UnauthorizedError(problem) : createApiError(problem);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
16
50
|
* The REST adapter set up for `laravel-panneau` with Laravel's session authentication: it sends
|
|
17
51
|
* cookies, adds the CSRF token from the `XSRF-TOKEN` cookie to every request, and treats `419`
|
|
18
52
|
* (expired CSRF token or session) as unauthorized.
|
|
@@ -21,17 +55,16 @@ function createLaravelAdapter({ xsrfCookie = "XSRF-TOKEN", xsrfHeader = "X-XSRF-
|
|
|
21
55
|
return createRestAdapter({
|
|
22
56
|
...options,
|
|
23
57
|
credentials,
|
|
24
|
-
headers: async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
createError: (problem, response) => createError?.(problem, response) ?? (problem.status === 419 ? new UnauthorizedError(problem) : void 0)
|
|
58
|
+
headers: async () => getLaravelHeaders({
|
|
59
|
+
headers: typeof headers === "function" ? await headers() : headers,
|
|
60
|
+
xsrfCookie,
|
|
61
|
+
xsrfHeader,
|
|
62
|
+
cookies
|
|
63
|
+
}),
|
|
64
|
+
createError: (problem, response) => createError?.(problem, response) ?? (problem.status === 419 ? createLaravelError(problem) : void 0)
|
|
32
65
|
});
|
|
33
66
|
}
|
|
34
67
|
//#endregion
|
|
35
|
-
export { createLaravelAdapter };
|
|
68
|
+
export { createLaravelAdapter, createLaravelError, getLaravelHeaders };
|
|
36
69
|
|
|
37
70
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/laravel/index.ts"],"sourcesContent":["import type { DataAdapter } from '../adapter';\nimport { UnauthorizedError } from '../errors';\nimport { type RestAdapterOptions, createRestAdapter } from '../rest';\n\nexport interface
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/laravel/index.ts"],"sourcesContent":["import type { Problem } from '@panneau/protocol';\n\nimport type { DataAdapter } from '../adapter';\nimport { type ApiError, UnauthorizedError, createApiError } from '../errors';\nimport { type RestAdapterOptions, createRestAdapter } from '../rest';\n\n/** How to send Laravel's CSRF token. */\nexport interface LaravelSessionOptions {\n /** Name of the cookie that holds the CSRF token. Defaults to Laravel's `XSRF-TOKEN`. */\n xsrfCookie?: string;\n /** Header that sends the CSRF token. Defaults to `X-XSRF-TOKEN`. */\n xsrfHeader?: string;\n /** Reads the cookies. Defaults to `document.cookie`, or no cookies outside a browser. */\n cookies?: () => string;\n}\n\nexport interface LaravelAdapterOptions extends RestAdapterOptions, LaravelSessionOptions {}\n\nfunction readCookie(cookies: string, name: string): string | null {\n for (const part of cookies.split(';')) {\n const [key, ...value] = part.trim().split('=');\n if (key === name) {\n try {\n return decodeURIComponent(value.join('='));\n } catch {\n return value.join('=');\n }\n }\n }\n return null;\n}\n\n/**\n * The headers of a request to a Laravel app with session authentication: `X-Requested-With`, so\n * that Laravel answers errors as JSON, and the CSRF token from the `XSRF-TOKEN` cookie. Use them,\n * with `credentials: 'include'`, for an app's own endpoints next to Panneau's.\n *\n * ```ts\n * fetch('/api/subscriptions', {\n * method: 'POST',\n * credentials: 'include',\n * headers: getLaravelHeaders({ headers: { 'Content-Type': 'application/json' } }),\n * body: JSON.stringify(values),\n * });\n * ```\n */\nexport function getLaravelHeaders({\n headers,\n xsrfCookie = 'XSRF-TOKEN',\n xsrfHeader = 'X-XSRF-TOKEN',\n cookies = () => (typeof document === 'undefined' ? '' : document.cookie),\n}: LaravelSessionOptions & { headers?: HeadersInit } = {}): Headers {\n const result = new Headers(headers);\n result.set('X-Requested-With', 'XMLHttpRequest');\n const token = readCookie(cookies(), xsrfCookie);\n if (token !== null) {\n result.set(xsrfHeader, token);\n }\n return result;\n}\n\n/**\n * The typed error of a failed response of a Laravel app: `createApiError()`, with `419` (an\n * expired CSRF token or session) as `UnauthorizedError`, like `401`.\n *\n * ```ts\n * if (!response.ok) {\n * throw createLaravelError(await readProblem(response));\n * }\n * ```\n */\nexport function createLaravelError(problem: Problem): ApiError {\n return problem.status === 419 ? new UnauthorizedError(problem) : createApiError(problem);\n}\n\n/**\n * The REST adapter set up for `laravel-panneau` with Laravel's session authentication: it sends\n * cookies, adds the CSRF token from the `XSRF-TOKEN` cookie to every request, and treats `419`\n * (expired CSRF token or session) as unauthorized.\n */\nexport function createLaravelAdapter({\n xsrfCookie = 'XSRF-TOKEN',\n xsrfHeader = 'X-XSRF-TOKEN',\n cookies = () => (typeof document === 'undefined' ? '' : document.cookie),\n headers,\n credentials = 'include',\n createError,\n ...options\n}: LaravelAdapterOptions): DataAdapter {\n return createRestAdapter({\n ...options,\n credentials,\n headers: async () =>\n getLaravelHeaders({\n headers: typeof headers === 'function' ? await headers() : headers,\n xsrfCookie,\n xsrfHeader,\n cookies,\n }),\n createError: (problem, response) =>\n createError?.(problem, response) ??\n (problem.status === 419 ? createLaravelError(problem) : undefined),\n });\n}\n"],"mappings":";;;AAkBA,SAAS,WAAW,SAAiB,MAA6B;CAC9D,KAAK,MAAM,QAAQ,QAAQ,MAAM,GAAG,GAAG;EACnC,MAAM,CAAC,KAAK,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG;EAC7C,IAAI,QAAQ,MACR,IAAI;GACA,OAAO,mBAAmB,MAAM,KAAK,GAAG,CAAC;EAC7C,QAAQ;GACJ,OAAO,MAAM,KAAK,GAAG;EACzB;CAER;CACA,OAAO;AACX;;;;;;;;;;;;;;;AAgBA,SAAgB,kBAAkB,EAC9B,SACA,aAAa,cACb,aAAa,gBACb,gBAAiB,OAAO,aAAa,cAAc,KAAK,SAAS,WACd,CAAC,GAAY;CAChE,MAAM,SAAS,IAAI,QAAQ,OAAO;CAClC,OAAO,IAAI,oBAAoB,gBAAgB;CAC/C,MAAM,QAAQ,WAAW,QAAQ,GAAG,UAAU;CAC9C,IAAI,UAAU,MACV,OAAO,IAAI,YAAY,KAAK;CAEhC,OAAO;AACX;;;;;;;;;;;AAYA,SAAgB,mBAAmB,SAA4B;CAC3D,OAAO,QAAQ,WAAW,MAAM,IAAI,kBAAkB,OAAO,IAAI,eAAe,OAAO;AAC3F;;;;;;AAOA,SAAgB,qBAAqB,EACjC,aAAa,cACb,aAAa,gBACb,gBAAiB,OAAO,aAAa,cAAc,KAAK,SAAS,QACjE,SACA,cAAc,WACd,aACA,GAAG,WACgC;CACnC,OAAO,kBAAkB;EACrB,GAAG;EACH;EACA,SAAS,YACL,kBAAkB;GACd,SAAS,OAAO,YAAY,aAAa,MAAM,QAAQ,IAAI;GAC3D;GACA;GACA;EACJ,CAAC;EACL,cAAc,SAAS,aACnB,cAAc,SAAS,QAAQ,MAC9B,QAAQ,WAAW,MAAM,mBAAmB,OAAO,IAAI,KAAA;CAChE,CAAC;AACL"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Problem } from '@panneau/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* The problem of a failed response: its body when it is one (`application/problem+json`), or a
|
|
4
|
+
* problem built from Laravel's `{ message, errors }` body, or from the status alone. Pass it to
|
|
5
|
+
* `createApiError()` to get the typed error (`ValidationError`, `UnauthorizedError`…) that Panneau
|
|
6
|
+
* forms and the app handle, for an app's own endpoints too.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const response = await fetch('/api/subscriptions', { method: 'POST', body });
|
|
10
|
+
* if (!response.ok) {
|
|
11
|
+
* throw createApiError(await readProblem(response));
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function readProblem(response: Response): Promise<Problem>;
|
|
16
|
+
//# sourceMappingURL=problem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../src/problem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BtE"}
|
package/dist/problem.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isProblem } from "@panneau/protocol/runtime";
|
|
2
|
+
//#region src/problem.ts
|
|
3
|
+
/**
|
|
4
|
+
* The problem of a failed response: its body when it is one (`application/problem+json`), or a
|
|
5
|
+
* problem built from Laravel's `{ message, errors }` body, or from the status alone. Pass it to
|
|
6
|
+
* `createApiError()` to get the typed error (`ValidationError`, `UnauthorizedError`…) that Panneau
|
|
7
|
+
* forms and the app handle, for an app's own endpoints too.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const response = await fetch('/api/subscriptions', { method: 'POST', body });
|
|
11
|
+
* if (!response.ok) {
|
|
12
|
+
* throw createApiError(await readProblem(response));
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
async function readProblem(response) {
|
|
17
|
+
const body = await response.clone().json().catch(() => null);
|
|
18
|
+
if (isProblem(body)) return body;
|
|
19
|
+
const record = body !== null && typeof body === "object" ? body : {};
|
|
20
|
+
const problem = {
|
|
21
|
+
type: "about:blank",
|
|
22
|
+
title: typeof record.title === "string" ? record.title : typeof record.message === "string" ? record.message : response.statusText || `HTTP ${response.status}`,
|
|
23
|
+
status: response.status
|
|
24
|
+
};
|
|
25
|
+
const errors = record.errors;
|
|
26
|
+
if (errors !== null && typeof errors === "object" && !Array.isArray(errors)) problem.errors = Object.fromEntries(Object.entries(errors).map(([path, messages]) => [path, (Array.isArray(messages) ? messages : [messages]).map(String)]));
|
|
27
|
+
return problem;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { readProblem };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=problem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"problem.js","names":[],"sources":["../src/problem.ts"],"sourcesContent":["import type { Problem } from '@panneau/protocol';\nimport { isProblem } from '@panneau/protocol/runtime';\n\n/**\n * The problem of a failed response: its body when it is one (`application/problem+json`), or a\n * problem built from Laravel's `{ message, errors }` body, or from the status alone. Pass it to\n * `createApiError()` to get the typed error (`ValidationError`, `UnauthorizedError`…) that Panneau\n * forms and the app handle, for an app's own endpoints too.\n *\n * ```ts\n * const response = await fetch('/api/subscriptions', { method: 'POST', body });\n * if (!response.ok) {\n * throw createApiError(await readProblem(response));\n * }\n * ```\n */\nexport async function readProblem(response: Response): Promise<Problem> {\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n if (isProblem(body)) {\n return body;\n }\n const record =\n body !== null && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n const problem: Problem = {\n type: 'about:blank',\n title:\n typeof record.title === 'string'\n ? record.title\n : typeof record.message === 'string'\n ? record.message\n : response.statusText || `HTTP ${response.status}`,\n status: response.status,\n };\n const errors = record.errors;\n if (errors !== null && typeof errors === 'object' && !Array.isArray(errors)) {\n problem.errors = Object.fromEntries(\n Object.entries(errors).map(([path, messages]) => [\n path,\n (Array.isArray(messages) ? messages : [messages]).map(String),\n ]),\n );\n }\n return problem;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,eAAsB,YAAY,UAAsC;CACpE,MAAM,OAAgB,MAAM,SACvB,MAAM,CAAC,CACP,KAAK,CAAC,CACN,YAAY,IAAI;CACrB,IAAI,UAAU,IAAI,GACd,OAAO;CAEX,MAAM,SACF,SAAS,QAAQ,OAAO,SAAS,WAAY,OAAmC,CAAC;CACrF,MAAM,UAAmB;EACrB,MAAM;EACN,OACI,OAAO,OAAO,UAAU,WAClB,OAAO,QACP,OAAO,OAAO,YAAY,WACxB,OAAO,UACP,SAAS,cAAc,QAAQ,SAAS;EACpD,QAAQ,SAAS;CACrB;CACA,MAAM,SAAS,OAAO;CACtB,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GACtE,QAAQ,SAAS,OAAO,YACpB,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,cAAc,CAC7C,OACC,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ,EAAA,CAAG,IAAI,MAAM,CAAC,CAChE,CACL;CAEJ,OAAO;AACX"}
|
package/dist/rest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,KAAK,OAAO,EAGf,MAAM,mBAAmB,CAAC;AAU3B,OAAO,KAAK,EAAkB,WAAW,EAAU,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,KAAK,QAAQ,EAAwC,MAAM,UAAU,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IAC/B,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,wFAAwF;IACxF,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,+DAA+D;IAC/D,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,GAAG,SAAS,CAAC;CAChF;AAsDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAiM1E"}
|
package/dist/rest.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InvalidResponseError, createApiError } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { readProblem } from "./problem.js";
|
|
3
|
+
import { ActionResult, ActionRun, Definition, PROBLEM_CONTENT_TYPE, toSearchParams, validate } from "@panneau/protocol";
|
|
3
4
|
//#region src/rest.ts
|
|
4
5
|
var ACCEPT = `application/json, ${PROBLEM_CONTENT_TYPE}`;
|
|
5
6
|
function isBlob(value) {
|
|
@@ -23,20 +24,6 @@ function appendFormData(data, key, value) {
|
|
|
23
24
|
function encode(path) {
|
|
24
25
|
return encodeURIComponent(path);
|
|
25
26
|
}
|
|
26
|
-
/** Builds the problem of a failed response, from its body when it is one. */
|
|
27
|
-
async function readProblem(response) {
|
|
28
|
-
const body = await response.clone().json().catch(() => null);
|
|
29
|
-
if (isProblem(body)) return body;
|
|
30
|
-
const record = body !== null && typeof body === "object" ? body : {};
|
|
31
|
-
const problem = {
|
|
32
|
-
type: "about:blank",
|
|
33
|
-
title: typeof record.title === "string" ? record.title : typeof record.message === "string" ? record.message : response.statusText || `HTTP ${response.status}`,
|
|
34
|
-
status: response.status
|
|
35
|
-
};
|
|
36
|
-
const errors = record.errors;
|
|
37
|
-
if (errors !== null && typeof errors === "object" && !Array.isArray(errors)) problem.errors = Object.fromEntries(Object.entries(errors).map(([path, messages]) => [path, (Array.isArray(messages) ? messages : [messages]).map(String)]));
|
|
38
|
-
return problem;
|
|
39
|
-
}
|
|
40
27
|
/**
|
|
41
28
|
* A `DataAdapter` over the HTTP API of the protocol. Errors (`application/problem+json`) reject
|
|
42
29
|
* with typed errors, updates send `If-Match` from the item version, and list queries use the
|
package/dist/rest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest.js","names":[],"sources":["../src/rest.ts"],"sourcesContent":["import {\n ActionResult,\n ActionRun,\n Definition,\n type ListQuery,\n PROBLEM_CONTENT_TYPE,\n type Problem,\n isProblem,\n toSearchParams,\n validate,\n} from '@panneau/protocol';\nimport type {\n Item,\n ListResponse,\n Media,\n MediaListResponse,\n SearchResponse,\n Session,\n} from '@panneau/protocol';\n\nimport type { ActionResponse, DataAdapter, Values } from './adapter';\nimport { type ApiError, InvalidResponseError, createApiError } from './errors';\n\nexport interface RestAdapterOptions {\n /** URL of the API, as `api.baseUrl` in the definition (`/panneau/api`). */\n baseUrl: string;\n /** The `fetch` function to use. Defaults to the global one. */\n fetch?: (input: string, init: RequestInit) => Promise<Response>;\n /** Headers added to every request, or a function that returns them for each request. */\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n /** Whether requests send cookies (`fetch`'s `credentials`). */\n credentials?: RequestCredentials;\n /**\n * Creates the error of a failed response. Return `undefined` to use the default mapping from\n * the status (`createApiError()`). The Laravel preset uses it for `419`.\n */\n createError?: (problem: Problem, response: Response) => ApiError | undefined;\n}\n\ninterface Request {\n method?: string;\n path: string;\n query?: URLSearchParams;\n body?: unknown;\n headers?: HeadersInit;\n signal?: AbortSignal | undefined;\n}\n\nconst ACCEPT = `application/json, ${PROBLEM_CONTENT_TYPE}`;\n\nfunction isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob;\n}\n\nfunction hasBlob(value: unknown): boolean {\n if (isBlob(value)) {\n return true;\n }\n if (Array.isArray(value)) {\n return value.some(hasBlob);\n }\n if (value !== null && typeof value === 'object') {\n return Object.values(value).some(hasBlob);\n }\n return false;\n}\n\n/** Appends a value to form data with bracket keys: `values[tags][]`, `selection[ids][]`. */\nfunction appendFormData(data: FormData, key: string, value: unknown) {\n if (value === undefined) {\n return;\n }\n if (isBlob(value)) {\n data.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => appendFormData(data, `${key}[]`, item));\n } else if (value !== null && typeof value === 'object') {\n Object.entries(value).forEach(([name, item]) =>\n appendFormData(data, key === '' ? name : `${key}[${name}]`, item),\n );\n } else if (typeof value === 'boolean') {\n data.append(key, value ? '1' : '0');\n } else {\n data.append(key, value === null ? '' : String(value));\n }\n}\n\nfunction encode(path: string): string {\n return encodeURIComponent(path);\n}\n\n/** Builds the problem of a failed response, from its body when it is one. */\nasync function readProblem(response: Response): Promise<Problem> {\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n if (isProblem(body)) {\n return body;\n }\n const record =\n body !== null && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n const problem: Problem = {\n type: 'about:blank',\n title:\n typeof record.title === 'string'\n ? record.title\n : typeof record.message === 'string'\n ? record.message\n : response.statusText || `HTTP ${response.status}`,\n status: response.status,\n };\n const errors = record.errors;\n if (errors !== null && typeof errors === 'object' && !Array.isArray(errors)) {\n problem.errors = Object.fromEntries(\n Object.entries(errors).map(([path, messages]) => [\n path,\n (Array.isArray(messages) ? messages : [messages]).map(String),\n ]),\n );\n }\n return problem;\n}\n\n/**\n * A `DataAdapter` over the HTTP API of the protocol. Errors (`application/problem+json`) reject\n * with typed errors, updates send `If-Match` from the item version, and list queries use the\n * protocol's query string format.\n */\nexport function createRestAdapter(options: RestAdapterOptions): DataAdapter {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));\n\n const send = async ({\n method = 'GET',\n path,\n query,\n body,\n headers: requestHeaders,\n signal,\n }: Request): Promise<Response> => {\n const headers = new Headers(\n typeof options.headers === 'function' ? await options.headers() : options.headers,\n );\n headers.set('Accept', ACCEPT);\n new Headers(requestHeaders).forEach((value, name) => headers.set(name, value));\n\n let payload: BodyInit | undefined;\n if (body !== undefined) {\n if (hasBlob(body)) {\n const data = new FormData();\n appendFormData(data, '', body);\n payload = data;\n } else {\n headers.set('Content-Type', 'application/json');\n payload = JSON.stringify(body);\n }\n }\n\n const search = query?.toString() ?? '';\n const init: RequestInit = { method, headers };\n if (payload !== undefined) {\n init.body = payload;\n }\n if (options.credentials !== undefined) {\n init.credentials = options.credentials;\n }\n if (signal !== undefined) {\n init.signal = signal;\n }\n const response = await fetcher(\n `${baseUrl}${path}${search !== '' ? `?${search}` : ''}`,\n init,\n );\n if (!response.ok) {\n const problem = await readProblem(response);\n throw options.createError?.(problem, response) ?? createApiError(problem);\n }\n return response;\n };\n\n const json = async <T>(request: Request): Promise<T> => {\n const response = await send(request);\n return (await response.json()) as T;\n };\n\n const data = async <T>(request: Request): Promise<T> => (await json<{ data: T }>(request)).data;\n\n return {\n async getDefinition({ signal } = {}) {\n const body = await json<unknown>({ path: '/definition', signal });\n const result = validate(Definition, body);\n if (!result.success) {\n throw new InvalidResponseError('The definition is invalid.', result.issues);\n }\n return result.data;\n },\n\n list(resource, query: ListQuery = {}, { signal } = {}) {\n return json<ListResponse>({\n path: `/resources/${encode(resource)}`,\n query: toSearchParams(query),\n signal,\n });\n },\n get(resource, id, { signal } = {}) {\n return data<Item>({ path: `/resources/${encode(resource)}/${encode(id)}`, signal });\n },\n create(resource, values, { signal } = {}) {\n return data<Item>({\n method: 'POST',\n path: `/resources/${encode(resource)}`,\n body: values,\n signal,\n });\n },\n update(resource, id, values, { version, signal } = {}) {\n return data<Item>({\n method: 'PATCH',\n path: `/resources/${encode(resource)}/${encode(id)}`,\n body: values,\n headers:\n version !== undefined && version !== null ? { 'If-Match': `\"${version}\"` } : {},\n signal,\n });\n },\n async delete(resource, id, { signal } = {}) {\n await send({\n method: 'DELETE',\n path: `/resources/${encode(resource)}/${encode(id)}`,\n signal,\n });\n },\n\n async runAction(resource, action, request, { signal } = {}) {\n const response = await send({\n method: 'POST',\n path: `/resources/${encode(resource)}/actions/${encode(action)}`,\n body: request,\n signal,\n });\n const body: unknown = response.status === 204 ? {} : await response.json();\n if (response.status === 202) {\n const run = validate(ActionRun, (body as { run?: unknown }).run);\n if (!run.success) {\n throw new InvalidResponseError('The action run is invalid.', run.issues);\n }\n return { run: run.data } satisfies ActionResponse;\n }\n const result = validate(ActionResult, body);\n if (!result.success) {\n throw new InvalidResponseError('The action result is invalid.', result.issues);\n }\n return { result: result.data } satisfies ActionResponse;\n },\n async getActionRun(id, { signal } = {}) {\n return (await json<{ run: ActionRun }>({ path: `/actions/runs/${encode(id)}`, signal }))\n .run;\n },\n listActionRuns({ signal } = {}) {\n return data<ActionRun[]>({ path: '/actions/runs', signal });\n },\n async cancelActionRun(id, { signal } = {}) {\n await send({ method: 'DELETE', path: `/actions/runs/${encode(id)}`, signal });\n },\n\n listMedia(query: ListQuery = {}, { signal } = {}) {\n return json<MediaListResponse>({\n path: '/media',\n query: toSearchParams(query),\n signal,\n });\n },\n getMedia(id, { signal } = {}) {\n return data<Media>({ path: `/media/${encode(id)}`, signal });\n },\n uploadMedia(file, values: Values = {}, { signal } = {}) {\n return data<Media>({\n method: 'POST',\n path: '/media',\n body: { ...values, file },\n signal,\n });\n },\n updateMedia(id, values, { signal } = {}) {\n return data<Media>({\n method: 'PATCH',\n path: `/media/${encode(id)}`,\n body: values,\n signal,\n });\n },\n async deleteMedia(id, { signal } = {}) {\n await send({ method: 'DELETE', path: `/media/${encode(id)}`, signal });\n },\n\n getSession({ signal } = {}) {\n return json<Session>({ path: '/auth/session', signal });\n },\n login(credentials, { signal } = {}) {\n return json<Session>({\n method: 'POST',\n path: '/auth/login',\n body: credentials,\n signal,\n });\n },\n async logout({ signal } = {}) {\n await send({ method: 'POST', path: '/auth/logout', signal });\n },\n\n search(q, { signal } = {}) {\n return json<SearchResponse>({\n path: '/search',\n query: new URLSearchParams({ q }),\n signal,\n });\n },\n getWidgetData(widget, { signal } = {}) {\n return json<unknown>({ path: `/dashboard/widgets/${encode(widget)}`, signal });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rest.js","names":[],"sources":["../src/rest.ts"],"sourcesContent":["import {\n ActionResult,\n ActionRun,\n Definition,\n type ListQuery,\n PROBLEM_CONTENT_TYPE,\n type Problem,\n toSearchParams,\n validate,\n} from '@panneau/protocol';\nimport type {\n Item,\n ListResponse,\n Media,\n MediaListResponse,\n SearchResponse,\n Session,\n} from '@panneau/protocol';\n\nimport type { ActionResponse, DataAdapter, Values } from './adapter';\nimport { type ApiError, InvalidResponseError, createApiError } from './errors';\nimport { readProblem } from './problem';\n\nexport interface RestAdapterOptions {\n /** URL of the API, as `api.baseUrl` in the definition (`/panneau/api`). */\n baseUrl: string;\n /** The `fetch` function to use. Defaults to the global one. */\n fetch?: (input: string, init: RequestInit) => Promise<Response>;\n /** Headers added to every request, or a function that returns them for each request. */\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n /** Whether requests send cookies (`fetch`'s `credentials`). */\n credentials?: RequestCredentials;\n /**\n * Creates the error of a failed response. Return `undefined` to use the default mapping from\n * the status (`createApiError()`). The Laravel preset uses it for `419`.\n */\n createError?: (problem: Problem, response: Response) => ApiError | undefined;\n}\n\ninterface Request {\n method?: string;\n path: string;\n query?: URLSearchParams;\n body?: unknown;\n headers?: HeadersInit;\n signal?: AbortSignal | undefined;\n}\n\nconst ACCEPT = `application/json, ${PROBLEM_CONTENT_TYPE}`;\n\nfunction isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob;\n}\n\nfunction hasBlob(value: unknown): boolean {\n if (isBlob(value)) {\n return true;\n }\n if (Array.isArray(value)) {\n return value.some(hasBlob);\n }\n if (value !== null && typeof value === 'object') {\n return Object.values(value).some(hasBlob);\n }\n return false;\n}\n\n/** Appends a value to form data with bracket keys: `values[tags][]`, `selection[ids][]`. */\nfunction appendFormData(data: FormData, key: string, value: unknown) {\n if (value === undefined) {\n return;\n }\n if (isBlob(value)) {\n data.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => appendFormData(data, `${key}[]`, item));\n } else if (value !== null && typeof value === 'object') {\n Object.entries(value).forEach(([name, item]) =>\n appendFormData(data, key === '' ? name : `${key}[${name}]`, item),\n );\n } else if (typeof value === 'boolean') {\n data.append(key, value ? '1' : '0');\n } else {\n data.append(key, value === null ? '' : String(value));\n }\n}\n\nfunction encode(path: string): string {\n return encodeURIComponent(path);\n}\n\n/**\n * A `DataAdapter` over the HTTP API of the protocol. Errors (`application/problem+json`) reject\n * with typed errors, updates send `If-Match` from the item version, and list queries use the\n * protocol's query string format.\n */\nexport function createRestAdapter(options: RestAdapterOptions): DataAdapter {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));\n\n const send = async ({\n method = 'GET',\n path,\n query,\n body,\n headers: requestHeaders,\n signal,\n }: Request): Promise<Response> => {\n const headers = new Headers(\n typeof options.headers === 'function' ? await options.headers() : options.headers,\n );\n headers.set('Accept', ACCEPT);\n new Headers(requestHeaders).forEach((value, name) => headers.set(name, value));\n\n let payload: BodyInit | undefined;\n if (body !== undefined) {\n if (hasBlob(body)) {\n const data = new FormData();\n appendFormData(data, '', body);\n payload = data;\n } else {\n headers.set('Content-Type', 'application/json');\n payload = JSON.stringify(body);\n }\n }\n\n const search = query?.toString() ?? '';\n const init: RequestInit = { method, headers };\n if (payload !== undefined) {\n init.body = payload;\n }\n if (options.credentials !== undefined) {\n init.credentials = options.credentials;\n }\n if (signal !== undefined) {\n init.signal = signal;\n }\n const response = await fetcher(\n `${baseUrl}${path}${search !== '' ? `?${search}` : ''}`,\n init,\n );\n if (!response.ok) {\n const problem = await readProblem(response);\n throw options.createError?.(problem, response) ?? createApiError(problem);\n }\n return response;\n };\n\n const json = async <T>(request: Request): Promise<T> => {\n const response = await send(request);\n return (await response.json()) as T;\n };\n\n const data = async <T>(request: Request): Promise<T> => (await json<{ data: T }>(request)).data;\n\n return {\n async getDefinition({ signal } = {}) {\n const body = await json<unknown>({ path: '/definition', signal });\n const result = validate(Definition, body);\n if (!result.success) {\n throw new InvalidResponseError('The definition is invalid.', result.issues);\n }\n return result.data;\n },\n\n list(resource, query: ListQuery = {}, { signal } = {}) {\n return json<ListResponse>({\n path: `/resources/${encode(resource)}`,\n query: toSearchParams(query),\n signal,\n });\n },\n get(resource, id, { signal } = {}) {\n return data<Item>({ path: `/resources/${encode(resource)}/${encode(id)}`, signal });\n },\n create(resource, values, { signal } = {}) {\n return data<Item>({\n method: 'POST',\n path: `/resources/${encode(resource)}`,\n body: values,\n signal,\n });\n },\n update(resource, id, values, { version, signal } = {}) {\n return data<Item>({\n method: 'PATCH',\n path: `/resources/${encode(resource)}/${encode(id)}`,\n body: values,\n headers:\n version !== undefined && version !== null ? { 'If-Match': `\"${version}\"` } : {},\n signal,\n });\n },\n async delete(resource, id, { signal } = {}) {\n await send({\n method: 'DELETE',\n path: `/resources/${encode(resource)}/${encode(id)}`,\n signal,\n });\n },\n\n async runAction(resource, action, request, { signal } = {}) {\n const response = await send({\n method: 'POST',\n path: `/resources/${encode(resource)}/actions/${encode(action)}`,\n body: request,\n signal,\n });\n const body: unknown = response.status === 204 ? {} : await response.json();\n if (response.status === 202) {\n const run = validate(ActionRun, (body as { run?: unknown }).run);\n if (!run.success) {\n throw new InvalidResponseError('The action run is invalid.', run.issues);\n }\n return { run: run.data } satisfies ActionResponse;\n }\n const result = validate(ActionResult, body);\n if (!result.success) {\n throw new InvalidResponseError('The action result is invalid.', result.issues);\n }\n return { result: result.data } satisfies ActionResponse;\n },\n async getActionRun(id, { signal } = {}) {\n return (await json<{ run: ActionRun }>({ path: `/actions/runs/${encode(id)}`, signal }))\n .run;\n },\n listActionRuns({ signal } = {}) {\n return data<ActionRun[]>({ path: '/actions/runs', signal });\n },\n async cancelActionRun(id, { signal } = {}) {\n await send({ method: 'DELETE', path: `/actions/runs/${encode(id)}`, signal });\n },\n\n listMedia(query: ListQuery = {}, { signal } = {}) {\n return json<MediaListResponse>({\n path: '/media',\n query: toSearchParams(query),\n signal,\n });\n },\n getMedia(id, { signal } = {}) {\n return data<Media>({ path: `/media/${encode(id)}`, signal });\n },\n uploadMedia(file, values: Values = {}, { signal } = {}) {\n return data<Media>({\n method: 'POST',\n path: '/media',\n body: { ...values, file },\n signal,\n });\n },\n updateMedia(id, values, { signal } = {}) {\n return data<Media>({\n method: 'PATCH',\n path: `/media/${encode(id)}`,\n body: values,\n signal,\n });\n },\n async deleteMedia(id, { signal } = {}) {\n await send({ method: 'DELETE', path: `/media/${encode(id)}`, signal });\n },\n\n getSession({ signal } = {}) {\n return json<Session>({ path: '/auth/session', signal });\n },\n login(credentials, { signal } = {}) {\n return json<Session>({\n method: 'POST',\n path: '/auth/login',\n body: credentials,\n signal,\n });\n },\n async logout({ signal } = {}) {\n await send({ method: 'POST', path: '/auth/logout', signal });\n },\n\n search(q, { signal } = {}) {\n return json<SearchResponse>({\n path: '/search',\n query: new URLSearchParams({ q }),\n signal,\n });\n },\n getWidgetData(widget, { signal } = {}) {\n return json<unknown>({ path: `/dashboard/widgets/${encode(widget)}`, signal });\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panneau/data",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.3",
|
|
4
4
|
"description": "The data layer of Panneau: the DataAdapter interface, the REST adapter of the protocol, its Laravel preset and TanStack Query hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"react": "^19.2.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@panneau/protocol": "5.0.0-alpha.
|
|
47
|
+
"@panneau/protocol": "5.0.0-alpha.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"@tanstack/react-query": {
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@panneau/core": "5.0.0-alpha.
|
|
58
|
+
"@panneau/core": "5.0.0-alpha.3"
|
|
59
59
|
}
|
|
60
60
|
}
|