@panneau/data 4.1.0 → 5.0.0-alpha.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/README.md +65 -0
- package/dist/adapter.d.ts +84 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +78 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -456
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -1794
- package/dist/laravel/index.d.ts +17 -0
- package/dist/laravel/index.d.ts.map +1 -0
- package/dist/laravel/index.js +37 -0
- package/dist/laravel/index.js.map +1 -0
- package/dist/query/context.d.ts +15 -0
- package/dist/query/context.d.ts.map +1 -0
- package/dist/query/context.js +34 -0
- package/dist/query/context.js.map +1 -0
- package/dist/query/hooks.d.ts +52 -0
- package/dist/query/hooks.d.ts.map +1 -0
- package/dist/query/hooks.js +299 -0
- package/dist/query/hooks.js.map +1 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +6 -0
- package/dist/query/keys.d.ts +17 -0
- package/dist/query/keys.d.ts.map +1 -0
- package/dist/query/keys.js +55 -0
- package/dist/query/keys.js.map +1 -0
- package/dist/query/options.d.ts +18 -0
- package/dist/query/options.d.ts.map +1 -0
- package/dist/query/options.js +30 -0
- package/dist/query/options.js.map +1 -0
- package/dist/query/refresh.d.ts +12 -0
- package/dist/query/refresh.d.ts.map +1 -0
- package/dist/query/refresh.js +24 -0
- package/dist/query/refresh.js.map +1 -0
- package/dist/rest.d.ts +25 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +243 -0
- package/dist/rest.js.map +1 -0
- package/package.json +40 -55
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @panneau/data
|
|
2
|
+
|
|
3
|
+
The data layer of Panneau: the `DataAdapter` interface, the REST adapter of the [protocol](../../docs/protocol.md), its Laravel preset and TanStack Query hooks.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { ConflictError, ValidationError, createRestAdapter } from '@panneau/data';
|
|
7
|
+
|
|
8
|
+
const adapter = createRestAdapter({ baseUrl: '/panneau/api' });
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await adapter.update('pages', item.id, { title: { fr: 'Accueil' } }, { version: item.version });
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error instanceof ConflictError) {
|
|
14
|
+
// Someone else saved the item: error.current is the current version.
|
|
15
|
+
} else if (error instanceof ValidationError) {
|
|
16
|
+
// error.errors: { 'title.en': ['This field is required.'] }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Export | Content |
|
|
22
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `@panneau/data` | The `DataAdapter` interface, `createRestAdapter()`, and the errors: `ApiError`, `ValidationError`, `ConflictError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError` |
|
|
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` |
|
|
26
|
+
|
|
27
|
+
## Adapters
|
|
28
|
+
|
|
29
|
+
A `DataAdapter` is everything the engine and the app need from a backend: the definition, resources (list, get, create, update, delete), actions and their runs, media, the auth session, search and widget data. Methods reject with an `ApiError` when the backend answers with an error.
|
|
30
|
+
|
|
31
|
+
- **`createRestAdapter({ baseUrl, fetch, headers, credentials })`** implements the HTTP API of the protocol:
|
|
32
|
+
- `application/problem+json` errors become typed errors, by status: `401` `UnauthorizedError`, `403` `ForbiddenError`, `404` `NotFoundError`, `412` `ConflictError` (with `current`), `422` `ValidationError` (with `errors` keyed by dot path), others `ApiError`. Responses that are not problems get a problem built from their status;
|
|
33
|
+
- `update()` sends `If-Match` from the `version` it is given;
|
|
34
|
+
- list queries use `toSearchParams()` from `@panneau/protocol`;
|
|
35
|
+
- values that contain a `Blob` (imports, uploads) are sent as `multipart/form-data`.
|
|
36
|
+
- **`createLaravelAdapter()`** is the preset for `laravel-panneau` with session authentication. Laravel specifics stay there, not in the protocol.
|
|
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
|
+
|
|
39
|
+
## Query hooks
|
|
40
|
+
|
|
41
|
+
The hooks need TanStack Query v5 and React (optional peer dependencies, only for this entry point), with a `QueryClientProvider` and a `DataAdapterProvider` above them.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
45
|
+
|
|
46
|
+
import { DataAdapterProvider, useResourceList, useUpdateItem } from '@panneau/data/query';
|
|
47
|
+
|
|
48
|
+
<QueryClientProvider client={queryClient}>
|
|
49
|
+
<DataAdapterProvider adapter={adapter}>
|
|
50
|
+
<Pages />
|
|
51
|
+
</DataAdapterProvider>
|
|
52
|
+
</QueryClientProvider>;
|
|
53
|
+
|
|
54
|
+
function Pages() {
|
|
55
|
+
const { data } = useResourceList('pages', { sort: ['-published_at'], page: 1 });
|
|
56
|
+
const update = useUpdateItem('pages');
|
|
57
|
+
// update.mutate({ id, values, version })
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Mutations update the cache: create and update cache the item and refetch the lists of the resource; delete removes the item and refetches the lists.
|
|
62
|
+
- `useRunAction()` refreshes what the action result asks (`refresh`: `none`, `items` or `list`, the default). A queued action returns a run instead: `useActionRun(run.id)` follows it until it finishes, then refreshes.
|
|
63
|
+
- `queryKeys` and the `*QueryOptions()` functions give the keys and options of the hooks, to prefetch or invalidate their data.
|
|
64
|
+
|
|
65
|
+
The package layout, scripts and API reports are explained in [`docs/packages.md`](../../docs/packages.md).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ActionRequest, ActionResult, ActionRun, Definition, Item, ListQuery, ListResponse, Media, MediaListResponse, SearchResponse, Session } from '@panneau/protocol';
|
|
2
|
+
/** Values of an item's fields, keyed by field name. */
|
|
3
|
+
export type Values = Record<string, unknown>;
|
|
4
|
+
export interface RequestOptions {
|
|
5
|
+
/** Aborts the request. TanStack Query passes one to cancel queries. */
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface UpdateOptions extends RequestOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The version of the item the values were edited from. The update fails with a
|
|
11
|
+
* `ConflictError` when the item changed since. `null` or omitted: no check.
|
|
12
|
+
*/
|
|
13
|
+
version?: string | null;
|
|
14
|
+
}
|
|
15
|
+
/** Answer of an action: its result, or a run when the backend queued it (`202`). */
|
|
16
|
+
export type ActionResponse = {
|
|
17
|
+
result: ActionResult;
|
|
18
|
+
run?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
run: ActionRun;
|
|
21
|
+
result?: undefined;
|
|
22
|
+
};
|
|
23
|
+
/** Query of the media list: a list query whose filters include `type` and `tags`. */
|
|
24
|
+
export type MediaQuery = ListQuery;
|
|
25
|
+
/** Credentials of a password login. */
|
|
26
|
+
export interface LoginCredentials {
|
|
27
|
+
email: string;
|
|
28
|
+
password: string;
|
|
29
|
+
remember?: boolean;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Everything Panneau needs from a backend. `createRestAdapter()` implements it over the HTTP API of
|
|
34
|
+
* the protocol. A product with its own API implements it to use the engine and the app.
|
|
35
|
+
*
|
|
36
|
+
* Methods reject with an `ApiError` (or one of its subclasses) when the backend answers with an
|
|
37
|
+
* error.
|
|
38
|
+
*/
|
|
39
|
+
export interface DataAdapter {
|
|
40
|
+
/** The definition for the current user (`GET /definition`), with defaults applied. */
|
|
41
|
+
getDefinition(options?: RequestOptions): Promise<Definition>;
|
|
42
|
+
/** A page of items of a resource. */
|
|
43
|
+
list(resource: string, query?: ListQuery, options?: RequestOptions): Promise<ListResponse>;
|
|
44
|
+
/** One item. */
|
|
45
|
+
get(resource: string, id: string, options?: RequestOptions): Promise<Item>;
|
|
46
|
+
/** Creates an item. Rejects with a `ValidationError` when values are invalid. */
|
|
47
|
+
create(resource: string, values: Values, options?: RequestOptions): Promise<Item>;
|
|
48
|
+
/**
|
|
49
|
+
* Updates some values of an item. With a `version`, rejects with a `ConflictError` when the
|
|
50
|
+
* item changed since that version.
|
|
51
|
+
*/
|
|
52
|
+
update(resource: string, id: string, values: Values, options?: UpdateOptions): Promise<Item>;
|
|
53
|
+
/** Deletes an item. */
|
|
54
|
+
delete(resource: string, id: string, options?: RequestOptions): Promise<void>;
|
|
55
|
+
/** Runs an action on a selection, or on the whole resource without a selection. */
|
|
56
|
+
runAction(resource: string, action: string, request: ActionRequest, options?: RequestOptions): Promise<ActionResponse>;
|
|
57
|
+
/** A run of a queued action, with its progress and, once finished, its result. */
|
|
58
|
+
getActionRun(id: string, options?: RequestOptions): Promise<ActionRun>;
|
|
59
|
+
/** The current user's recent runs. */
|
|
60
|
+
listActionRuns(options?: RequestOptions): Promise<ActionRun[]>;
|
|
61
|
+
/** Cancels a run that hasn't finished. */
|
|
62
|
+
cancelActionRun(id: string, options?: RequestOptions): Promise<void>;
|
|
63
|
+
/** A page of the media library. */
|
|
64
|
+
listMedia(query?: MediaQuery, options?: RequestOptions): Promise<MediaListResponse>;
|
|
65
|
+
/** One media item. */
|
|
66
|
+
getMedia(id: string, options?: RequestOptions): Promise<Media>;
|
|
67
|
+
/** Uploads a file, with optional metadata. */
|
|
68
|
+
uploadMedia(file: Blob, values?: Values, options?: RequestOptions): Promise<Media>;
|
|
69
|
+
/** Updates the metadata or the transform of a media item. */
|
|
70
|
+
updateMedia(id: string, values: Values, options?: RequestOptions): Promise<Media>;
|
|
71
|
+
/** Deletes a media item. */
|
|
72
|
+
deleteMedia(id: string, options?: RequestOptions): Promise<void>;
|
|
73
|
+
/** The current session. Rejects with an `UnauthorizedError` without one. */
|
|
74
|
+
getSession(options?: RequestOptions): Promise<Session>;
|
|
75
|
+
/** Logs in with a password. */
|
|
76
|
+
login(credentials: LoginCredentials, options?: RequestOptions): Promise<Session>;
|
|
77
|
+
/** Logs out. */
|
|
78
|
+
logout(options?: RequestOptions): Promise<void>;
|
|
79
|
+
/** Searches every searchable resource. */
|
|
80
|
+
search(q: string, options?: RequestOptions): Promise<SearchResponse>;
|
|
81
|
+
/** Data of a dashboard widget, in the shape its type defines. */
|
|
82
|
+
getWidgetData(widget: string, options?: RequestOptions): Promise<unknown>;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Item, Problem, ValidationIssue } from '@panneau/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* An error response of the API, parsed from `application/problem+json`. Subclasses cover the
|
|
4
|
+
* statuses a client handles differently. Other statuses (`409`, `429`, `5xx`…) are an `ApiError`.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiError extends Error {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
/** The HTTP status. */
|
|
9
|
+
readonly status: number;
|
|
10
|
+
/** The problem details sent by the backend, or built from the status when it sent none. */
|
|
11
|
+
readonly problem: Problem;
|
|
12
|
+
constructor(problem: Problem, options?: ErrorOptions);
|
|
13
|
+
}
|
|
14
|
+
/** `422`: the submitted values are invalid. */
|
|
15
|
+
export declare class ValidationError extends ApiError {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** Messages keyed by the dot path of the invalid value (`title.fr`, `blocks.2.image`). */
|
|
18
|
+
readonly errors: Record<string, string[]>;
|
|
19
|
+
constructor(problem: Problem, options?: ErrorOptions);
|
|
20
|
+
}
|
|
21
|
+
/** `412`: the item changed since the version sent in `If-Match`. */
|
|
22
|
+
export declare class ConflictError extends ApiError {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** The current version of the item, when the backend sent it. */
|
|
25
|
+
readonly current: Item | null;
|
|
26
|
+
constructor(problem: Problem, options?: ErrorOptions);
|
|
27
|
+
}
|
|
28
|
+
/** `401` (or `419` with the Laravel preset): the user is not authenticated. */
|
|
29
|
+
export declare class UnauthorizedError extends ApiError {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
}
|
|
32
|
+
/** `403`: the user is not allowed to do this. */
|
|
33
|
+
export declare class ForbiddenError extends ApiError {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
}
|
|
36
|
+
/** `404`: the resource, item or route doesn't exist. */
|
|
37
|
+
export declare class NotFoundError extends ApiError {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
}
|
|
40
|
+
/** A successful response whose body doesn't match the protocol. */
|
|
41
|
+
export declare class InvalidResponseError extends Error {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
/** What doesn't match, with dot paths into the body. */
|
|
44
|
+
readonly issues: ValidationIssue[];
|
|
45
|
+
constructor(message: string, issues: ValidationIssue[]);
|
|
46
|
+
}
|
|
47
|
+
/** Creates the error that matches the status of a problem. */
|
|
48
|
+
export declare function createApiError(problem: Problem, options?: ErrorOptions): ApiError;
|
|
49
|
+
/** Whether a value is an error of the API. */
|
|
50
|
+
export declare function isApiError(error: unknown): error is ApiError;
|
|
51
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;GAGG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAC/B,SAAkB,IAAI,EAAE,MAAM,CAAc;IAC5C,uBAAuB;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY;CAKvD;AAED,+CAA+C;AAC/C,qBAAa,eAAgB,SAAQ,QAAQ;IACzC,SAAkB,IAAI,EAAE,MAAM,CAAqB;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;gBAE9B,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY;CAIvD;AAED,oEAAoE;AACpE,qBAAa,aAAc,SAAQ,QAAQ;IACvC,SAAkB,IAAI,EAAE,MAAM,CAAmB;IACjD,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;gBAElB,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY;CAIvD;AAED,+EAA+E;AAC/E,qBAAa,iBAAkB,SAAQ,QAAQ;IAC3C,SAAkB,IAAI,EAAE,MAAM,CAAuB;CACxD;AAED,iDAAiD;AACjD,qBAAa,cAAe,SAAQ,QAAQ;IACxC,SAAkB,IAAI,EAAE,MAAM,CAAoB;CACrD;AAED,wDAAwD;AACxD,qBAAa,aAAc,SAAQ,QAAQ;IACvC,SAAkB,IAAI,EAAE,MAAM,CAAmB;CACpD;AAED,mEAAmE;AACnE,qBAAa,oBAAqB,SAAQ,KAAK;IAC3C,SAAkB,IAAI,EAAE,MAAM,CAA0B;IACxD,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE;CAIzD;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,QAAQ,CAejF;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* An error response of the API, parsed from `application/problem+json`. Subclasses cover the
|
|
4
|
+
* statuses a client handles differently. Other statuses (`409`, `429`, `5xx`…) are an `ApiError`.
|
|
5
|
+
*/
|
|
6
|
+
var ApiError = class extends Error {
|
|
7
|
+
name = "ApiError";
|
|
8
|
+
/** The HTTP status. */
|
|
9
|
+
status;
|
|
10
|
+
/** The problem details sent by the backend, or built from the status when it sent none. */
|
|
11
|
+
problem;
|
|
12
|
+
constructor(problem, options) {
|
|
13
|
+
super(problem.detail ?? problem.title, options);
|
|
14
|
+
this.status = problem.status;
|
|
15
|
+
this.problem = problem;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/** `422`: the submitted values are invalid. */
|
|
19
|
+
var ValidationError = class extends ApiError {
|
|
20
|
+
name = "ValidationError";
|
|
21
|
+
/** Messages keyed by the dot path of the invalid value (`title.fr`, `blocks.2.image`). */
|
|
22
|
+
errors;
|
|
23
|
+
constructor(problem, options) {
|
|
24
|
+
super(problem, options);
|
|
25
|
+
this.errors = problem.errors ?? {};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/** `412`: the item changed since the version sent in `If-Match`. */
|
|
29
|
+
var ConflictError = class extends ApiError {
|
|
30
|
+
name = "ConflictError";
|
|
31
|
+
/** The current version of the item, when the backend sent it. */
|
|
32
|
+
current;
|
|
33
|
+
constructor(problem, options) {
|
|
34
|
+
super(problem, options);
|
|
35
|
+
this.current = problem.current ?? null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/** `401` (or `419` with the Laravel preset): the user is not authenticated. */
|
|
39
|
+
var UnauthorizedError = class extends ApiError {
|
|
40
|
+
name = "UnauthorizedError";
|
|
41
|
+
};
|
|
42
|
+
/** `403`: the user is not allowed to do this. */
|
|
43
|
+
var ForbiddenError = class extends ApiError {
|
|
44
|
+
name = "ForbiddenError";
|
|
45
|
+
};
|
|
46
|
+
/** `404`: the resource, item or route doesn't exist. */
|
|
47
|
+
var NotFoundError = class extends ApiError {
|
|
48
|
+
name = "NotFoundError";
|
|
49
|
+
};
|
|
50
|
+
/** A successful response whose body doesn't match the protocol. */
|
|
51
|
+
var InvalidResponseError = class extends Error {
|
|
52
|
+
name = "InvalidResponseError";
|
|
53
|
+
/** What doesn't match, with dot paths into the body. */
|
|
54
|
+
issues;
|
|
55
|
+
constructor(message, issues) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.issues = issues;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/** Creates the error that matches the status of a problem. */
|
|
61
|
+
function createApiError(problem, options) {
|
|
62
|
+
switch (problem.status) {
|
|
63
|
+
case 401: return new UnauthorizedError(problem, options);
|
|
64
|
+
case 403: return new ForbiddenError(problem, options);
|
|
65
|
+
case 404: return new NotFoundError(problem, options);
|
|
66
|
+
case 412: return new ConflictError(problem, options);
|
|
67
|
+
case 422: return new ValidationError(problem, options);
|
|
68
|
+
default: return new ApiError(problem, options);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Whether a value is an error of the API. */
|
|
72
|
+
function isApiError(error) {
|
|
73
|
+
return error instanceof ApiError;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, isApiError };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import type { Item, Problem, ValidationIssue } from '@panneau/protocol';\n\n/**\n * An error response of the API, parsed from `application/problem+json`. Subclasses cover the\n * statuses a client handles differently. Other statuses (`409`, `429`, `5xx`…) are an `ApiError`.\n */\nexport class ApiError extends Error {\n override readonly name: string = 'ApiError';\n /** The HTTP status. */\n readonly status: number;\n /** The problem details sent by the backend, or built from the status when it sent none. */\n readonly problem: Problem;\n\n constructor(problem: Problem, options?: ErrorOptions) {\n super(problem.detail ?? problem.title, options);\n this.status = problem.status;\n this.problem = problem;\n }\n}\n\n/** `422`: the submitted values are invalid. */\nexport class ValidationError extends ApiError {\n override readonly name: string = 'ValidationError';\n /** Messages keyed by the dot path of the invalid value (`title.fr`, `blocks.2.image`). */\n readonly errors: Record<string, string[]>;\n\n constructor(problem: Problem, options?: ErrorOptions) {\n super(problem, options);\n this.errors = problem.errors ?? {};\n }\n}\n\n/** `412`: the item changed since the version sent in `If-Match`. */\nexport class ConflictError extends ApiError {\n override readonly name: string = 'ConflictError';\n /** The current version of the item, when the backend sent it. */\n readonly current: Item | null;\n\n constructor(problem: Problem, options?: ErrorOptions) {\n super(problem, options);\n this.current = problem.current ?? null;\n }\n}\n\n/** `401` (or `419` with the Laravel preset): the user is not authenticated. */\nexport class UnauthorizedError extends ApiError {\n override readonly name: string = 'UnauthorizedError';\n}\n\n/** `403`: the user is not allowed to do this. */\nexport class ForbiddenError extends ApiError {\n override readonly name: string = 'ForbiddenError';\n}\n\n/** `404`: the resource, item or route doesn't exist. */\nexport class NotFoundError extends ApiError {\n override readonly name: string = 'NotFoundError';\n}\n\n/** A successful response whose body doesn't match the protocol. */\nexport class InvalidResponseError extends Error {\n override readonly name: string = 'InvalidResponseError';\n /** What doesn't match, with dot paths into the body. */\n readonly issues: ValidationIssue[];\n\n constructor(message: string, issues: ValidationIssue[]) {\n super(message);\n this.issues = issues;\n }\n}\n\n/** Creates the error that matches the status of a problem. */\nexport function createApiError(problem: Problem, options?: ErrorOptions): ApiError {\n switch (problem.status) {\n case 401:\n return new UnauthorizedError(problem, options);\n case 403:\n return new ForbiddenError(problem, options);\n case 404:\n return new NotFoundError(problem, options);\n case 412:\n return new ConflictError(problem, options);\n case 422:\n return new ValidationError(problem, options);\n default:\n return new ApiError(problem, options);\n }\n}\n\n/** Whether a value is an error of the API. */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n"],"mappings":";;;;;AAMA,IAAa,WAAb,cAA8B,MAAM;CAChC,OAAiC;;CAEjC;;CAEA;CAEA,YAAY,SAAkB,SAAwB;EAClD,MAAM,QAAQ,UAAU,QAAQ,OAAO,OAAO;EAC9C,KAAK,SAAS,QAAQ;EACtB,KAAK,UAAU;CACnB;AACJ;;AAGA,IAAa,kBAAb,cAAqC,SAAS;CAC1C,OAAiC;;CAEjC;CAEA,YAAY,SAAkB,SAAwB;EAClD,MAAM,SAAS,OAAO;EACtB,KAAK,SAAS,QAAQ,UAAU,CAAC;CACrC;AACJ;;AAGA,IAAa,gBAAb,cAAmC,SAAS;CACxC,OAAiC;;CAEjC;CAEA,YAAY,SAAkB,SAAwB;EAClD,MAAM,SAAS,OAAO;EACtB,KAAK,UAAU,QAAQ,WAAW;CACtC;AACJ;;AAGA,IAAa,oBAAb,cAAuC,SAAS;CAC5C,OAAiC;AACrC;;AAGA,IAAa,iBAAb,cAAoC,SAAS;CACzC,OAAiC;AACrC;;AAGA,IAAa,gBAAb,cAAmC,SAAS;CACxC,OAAiC;AACrC;;AAGA,IAAa,uBAAb,cAA0C,MAAM;CAC5C,OAAiC;;CAEjC;CAEA,YAAY,SAAiB,QAA2B;EACpD,MAAM,OAAO;EACb,KAAK,SAAS;CAClB;AACJ;;AAGA,SAAgB,eAAe,SAAkB,SAAkC;CAC/E,QAAQ,QAAQ,QAAhB;EACI,KAAK,KACD,OAAO,IAAI,kBAAkB,SAAS,OAAO;EACjD,KAAK,KACD,OAAO,IAAI,eAAe,SAAS,OAAO;EAC9C,KAAK,KACD,OAAO,IAAI,cAAc,SAAS,OAAO;EAC7C,KAAK,KACD,OAAO,IAAI,cAAc,SAAS,OAAO;EAC7C,KAAK,KACD,OAAO,IAAI,gBAAgB,SAAS,OAAO;EAC/C,SACI,OAAO,IAAI,SAAS,SAAS,OAAO;CAC5C;AACJ;;AAGA,SAAgB,WAAW,OAAmC;CAC1D,OAAO,iBAAiB;AAC5B"}
|