@panneau/data 5.0.0-alpha.3 → 5.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,11 +22,12 @@ try {
22
22
  | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
23
  | `@panneau/data` | The `DataAdapter` interface, `createRestAdapter()`, the errors (`ApiError`, `ValidationError`, `ConflictError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`), `createApiError()` and `readProblem()` |
24
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/uploads` | `createPresignedUploads()`: direct uploads to S3 with URLs that the backend signs, for Uppy 6's `@uppy/aws-s3` |
25
26
  | `@panneau/data/query` | `DataAdapterProvider` and the TanStack Query hooks: `useDefinition`, `useResourceList`, `useResourceItem`, `useCreateItem`, `useUpdateItem`, `useDeleteItem`, `useRunAction`, `useActionRun` |
26
27
 
27
28
  ## Adapters
28
29
 
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
+ 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, the auth session, search and widget data. Media are items of resources of kind `media` ([0026](../../docs/decisions/0026-media-libraries.md)), through the resource methods. Methods reject with an `ApiError` when the backend answers with an error.
30
31
 
31
32
  - **`createRestAdapter({ baseUrl, fetch, headers, credentials })`** implements the HTTP API of the protocol:
32
33
  - `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;
@@ -59,6 +60,33 @@ if (!response.ok) {
59
60
  - `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
61
  - `getLaravelHeaders({ headers, xsrfCookie, xsrfHeader, cookies })` adds `X-Requested-With` and the CSRF token from Laravel's cookie.
61
62
 
63
+ ### Direct uploads
64
+
65
+ Large files (videos of several gigabytes) go from the browser to S3-compatible storage, in parts, with URLs that the backend signs: the `presigned` mode of the protocol ([0025](../../docs/decisions/0025-presigned-uploads.md)). `createPresignedUploads()` gives Uppy 6 its `signRequest`, and completes each upload. An upload is not a media: values reference it as `{ upload: 'upl_…' }`, such as the `file` of a media.
66
+
67
+ ```ts
68
+ import AwsS3 from '@uppy/aws-s3';
69
+ import Uppy from '@uppy/core';
70
+
71
+ import { createLaravelError, getLaravelHeaders } from '@panneau/data/laravel';
72
+ import { createPresignedUploads } from '@panneau/data/uploads';
73
+
74
+ const uploads = createPresignedUploads({
75
+ baseUrl: definition.api.baseUrl, // the routes are under {baseUrl}/uploads, or at uploads.endpoint
76
+ headers: () => getLaravelHeaders(),
77
+ createError: createLaravelError,
78
+ });
79
+ const uppy = new Uppy().use(AwsS3, { signRequest: uploads.signRequest });
80
+ uppy.on('upload-success', async (file, response) => {
81
+ const upload = await uploads.complete(response.body.key, { name: file.name });
82
+ const media = await adapter.create('media', { file: { upload: upload.id } }); // media.media.status: 'processing'
83
+ });
84
+ ```
85
+
86
+ It is plain `fetch` and doesn't import Uppy. Without direct uploads, `create()` sends a file in `multipart/form-data`.
87
+
88
+ The REST adapter gives the same object, with its own base URL, headers, credentials and errors: `adapter.presignedUploads({ endpoint: definition.uploads.endpoint })` (optional in `DataAdapter`). The fields of `@panneau/fields` and `@panneau/media` upload through it with `createUppyUploadSource()` of `@panneau/fields/uppy`.
89
+
62
90
  ## Query hooks
63
91
 
64
92
  The hooks need TanStack Query v5 and React (optional peer dependencies, only for this entry point), with a `QueryClientProvider` and a `DataAdapterProvider` above them.
@@ -83,6 +111,7 @@ function Pages() {
83
111
 
84
112
  - 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.
85
113
  - `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.
114
+ - `useDataAdapter()` gives the adapter of `DataAdapterProvider`, and throws without one. `useOptionalDataAdapter()` gives `null` instead, for components that also work without a backend, such as the media field.
86
115
  - `queryKeys` and the `*QueryOptions()` functions give the keys and options of the hooks, to prefetch or invalidate their data.
87
116
 
88
117
  The package layout, scripts and API reports are explained in [`docs/packages.md`](../../docs/packages.md).
package/dist/adapter.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { ActionRequest, ActionResult, ActionRun, Definition, Item, ListQuery, ListResponse, Media, MediaListResponse, SearchResponse, Session } from '@panneau/protocol';
1
+ import type { ActionRequest, ActionResult, ActionRun, Definition, Item, ListQuery, ListResponse, SearchResponse, Session } from '@panneau/protocol';
2
+ import type { PresignedUploads } from './uploads';
2
3
  /** Values of an item's fields, keyed by field name. */
3
4
  export type Values = Record<string, unknown>;
4
5
  export interface RequestOptions {
@@ -20,8 +21,6 @@ export type ActionResponse = {
20
21
  run: ActionRun;
21
22
  result?: undefined;
22
23
  };
23
- /** Query of the media list: a list query whose filters include `type` and `tags`. */
24
- export type MediaQuery = ListQuery;
25
24
  /** Credentials of a password login. */
26
25
  export interface LoginCredentials {
27
26
  email: string;
@@ -60,16 +59,16 @@ export interface DataAdapter {
60
59
  listActionRuns(options?: RequestOptions): Promise<ActionRun[]>;
61
60
  /** Cancels a run that hasn't finished. */
62
61
  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>;
62
+ /**
63
+ * Direct uploads to storage through this adapter, with its headers, cookies and errors, when
64
+ * the definition announces `"uploads": { "mode": "presigned" }`. `endpoint` is the definition's
65
+ * `uploads.endpoint`, when the routes are not under the API. Optional: an adapter without it
66
+ * sends files in multipart requests only. Media are items of resources of kind `media`
67
+ * (decision 0026): `create()` makes one from `{ file: { upload } }`.
68
+ */
69
+ presignedUploads?(options?: {
70
+ endpoint?: string;
71
+ }): PresignedUploads;
73
72
  /** The current session. Rejects with an `UnauthorizedError` without one. */
74
73
  getSession(options?: RequestOptions): Promise<Session>;
75
74
  /** Logs in with a password. */
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export type { ActionResponse, DataAdapter, LoginCredentials, MediaQuery, RequestOptions, UpdateOptions, Values, } from './adapter';
1
+ export type { ActionResponse, DataAdapter, LoginCredentials, RequestOptions, UpdateOptions, Values, } from './adapter';
2
2
  export { ApiError, ConflictError, ForbiddenError, InvalidResponseError, NotFoundError, UnauthorizedError, ValidationError, createApiError, isApiError, } from './errors';
3
3
  export { readProblem } from './problem';
4
4
  export { type RestAdapterOptions, createRestAdapter } from './rest';
5
+ export type { PresignableRequest, PresignedResponse, PresignedUploads } from './uploads';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -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,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,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,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;AACpE,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
@@ -12,4 +12,9 @@ export interface DataAdapterProviderProps {
12
12
  export declare function DataAdapterProvider({ adapter, children }: DataAdapterProviderProps): import("react").JSX.Element;
13
13
  /** The adapter of the closest `DataAdapterProvider`. */
14
14
  export declare function useDataAdapter(): DataAdapter;
15
+ /**
16
+ * The adapter of the closest `DataAdapterProvider`, or `null` without one: for components that
17
+ * also work without a backend, such as fields that can upload.
18
+ */
19
+ export declare function useOptionalDataAdapter(): DataAdapter | null;
15
20
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/query/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,WAAW,wBAAwB;IACrC,2FAA2F;IAC3F,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BAElF;AAED,wDAAwD;AACxD,wBAAgB,cAAc,IAAI,WAAW,CAM5C"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/query/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,WAAW,wBAAwB;IACrC,2FAA2F;IAC3F,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BAElF;AAED,wDAAwD;AACxD,wBAAgB,cAAc,IAAI,WAAW,CAM5C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,WAAW,GAAG,IAAI,CAE3D"}
@@ -28,7 +28,14 @@ function useDataAdapter() {
28
28
  if (adapter === null) throw new Error("useDataAdapter() needs a <DataAdapterProvider> above it.");
29
29
  return adapter;
30
30
  }
31
+ /**
32
+ * The adapter of the closest `DataAdapterProvider`, or `null` without one: for components that
33
+ * also work without a backend, such as fields that can upload.
34
+ */
35
+ function useOptionalDataAdapter() {
36
+ return use(DataAdapterContext);
37
+ }
31
38
  //#endregion
32
- export { DataAdapterProvider, useDataAdapter };
39
+ export { DataAdapterProvider, useDataAdapter, useOptionalDataAdapter };
33
40
 
34
41
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":["adapter","children","_c"],"sources":["../../src/query/context.tsx"],"sourcesContent":["import { type ReactNode, createContext, use } from 'react';\n\nimport type { DataAdapter } from '../adapter';\n\nconst DataAdapterContext = createContext<DataAdapter | null>(null);\n\nexport interface DataAdapterProviderProps {\n /** The adapter of the hooks below: `createRestAdapter()`, a preset or a custom adapter. */\n adapter: DataAdapter;\n children?: ReactNode;\n}\n\n/**\n * Provides the adapter used by the query hooks. The hooks also need TanStack Query's\n * `QueryClientProvider` above them.\n */\nexport function DataAdapterProvider({ adapter, children }: DataAdapterProviderProps) {\n return <DataAdapterContext value={adapter}>{children}</DataAdapterContext>;\n}\n\n/** The adapter of the closest `DataAdapterProvider`. */\nexport function useDataAdapter(): DataAdapter {\n const adapter = use(DataAdapterContext);\n if (adapter === null) {\n throw new Error('useDataAdapter() needs a <DataAdapterProvider> above it.');\n }\n return adapter;\n}\n"],"mappings":";;;;AAIA,IAAM,qBAAqB,cAAkC,IAAI;;;;;AAYjE,SAAO,oBAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CAA6B,MAAA,EAAA,SAAA,aAAA;CAA+C,IAAA;CAAA,IAAA,EAAA,OAAA,WAAA,EAAA,OAAA,UAAA;EACxE,KAAA,oBAAC,oBAAD;GAA2BF,OAAA;GAAU;EAAlB,CAAA;EAAgD,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAnE;AAAmE;;AAI9E,SAAgB,iBAA8B;CAC1C,MAAM,UAAU,IAAI,kBAAkB;CACtC,IAAI,YAAY,MACZ,MAAM,IAAI,MAAM,0DAA0D;CAE9E,OAAO;AACX"}
1
+ {"version":3,"file":"context.js","names":["adapter","children","_c"],"sources":["../../src/query/context.tsx"],"sourcesContent":["import { type ReactNode, createContext, use } from 'react';\n\nimport type { DataAdapter } from '../adapter';\n\nconst DataAdapterContext = createContext<DataAdapter | null>(null);\n\nexport interface DataAdapterProviderProps {\n /** The adapter of the hooks below: `createRestAdapter()`, a preset or a custom adapter. */\n adapter: DataAdapter;\n children?: ReactNode;\n}\n\n/**\n * Provides the adapter used by the query hooks. The hooks also need TanStack Query's\n * `QueryClientProvider` above them.\n */\nexport function DataAdapterProvider({ adapter, children }: DataAdapterProviderProps) {\n return <DataAdapterContext value={adapter}>{children}</DataAdapterContext>;\n}\n\n/** The adapter of the closest `DataAdapterProvider`. */\nexport function useDataAdapter(): DataAdapter {\n const adapter = use(DataAdapterContext);\n if (adapter === null) {\n throw new Error('useDataAdapter() needs a <DataAdapterProvider> above it.');\n }\n return adapter;\n}\n\n/**\n * The adapter of the closest `DataAdapterProvider`, or `null` without one: for components that\n * also work without a backend, such as fields that can upload.\n */\nexport function useOptionalDataAdapter(): DataAdapter | null {\n return use(DataAdapterContext);\n}\n"],"mappings":";;;;AAIA,IAAM,qBAAqB,cAAkC,IAAI;;;;;AAYjE,SAAO,oBAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CAA6B,MAAA,EAAA,SAAA,aAAA;CAA+C,IAAA;CAAA,IAAA,EAAA,OAAA,WAAA,EAAA,OAAA,UAAA;EACxE,KAAA,oBAAC,oBAAD;GAA2BF,OAAA;GAAU;EAAlB,CAAA;EAAgD,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAnE;AAAmE;;AAI9E,SAAgB,iBAA8B;CAC1C,MAAM,UAAU,IAAI,kBAAkB;CACtC,IAAI,YAAY,MACZ,MAAM,IAAI,MAAM,0DAA0D;CAE9E,OAAO;AACX;;;;;AAMA,SAAgB,yBAA6C;CACzD,OAAO,IAAI,kBAAkB;AACjC"}
@@ -1,4 +1,4 @@
1
- export { DataAdapterProvider, type DataAdapterProviderProps, useDataAdapter } from './context';
1
+ export { DataAdapterProvider, type DataAdapterProviderProps, useDataAdapter, useOptionalDataAdapter, } from './context';
2
2
  export { type QueryHookOptions, type RunActionVariables, type UpdateItemVariables, type UseActionRunOptions, isRunFinished, useActionRun, useCreateItem, useDefinition, useDeleteItem, useResourceItem, useResourceList, useRunAction, useUpdateItem, } from './hooks';
3
3
  export { queryKeys } from './keys';
4
4
  export { type DataQueryOptions, definitionQueryOptions, resourceItemQueryOptions, resourceListQueryOptions, } from './options';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC/F,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,aAAa,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACH,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,cAAc,EACd,sBAAsB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,aAAa,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACH,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
@@ -1,6 +1,6 @@
1
- import { DataAdapterProvider, useDataAdapter } from "./context.js";
1
+ import { DataAdapterProvider, useDataAdapter, useOptionalDataAdapter } from "./context.js";
2
2
  import { queryKeys } from "./keys.js";
3
3
  import { definitionQueryOptions, resourceItemQueryOptions, resourceListQueryOptions } from "./options.js";
4
4
  import { invalidateActionResult } from "./refresh.js";
5
5
  import { isRunFinished, useActionRun, useCreateItem, useDefinition, useDeleteItem, useResourceItem, useResourceList, useRunAction, useUpdateItem } from "./hooks.js";
6
- export { DataAdapterProvider, definitionQueryOptions, invalidateActionResult, isRunFinished, queryKeys, resourceItemQueryOptions, resourceListQueryOptions, useActionRun, useCreateItem, useDataAdapter, useDefinition, useDeleteItem, useResourceItem, useResourceList, useRunAction, useUpdateItem };
6
+ export { DataAdapterProvider, definitionQueryOptions, invalidateActionResult, isRunFinished, queryKeys, resourceItemQueryOptions, resourceListQueryOptions, useActionRun, useCreateItem, useDataAdapter, useDefinition, useDeleteItem, useOptionalDataAdapter, useResourceItem, useResourceList, useRunAction, useUpdateItem };
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,KAAK,OAAO,EAGf,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,KAAK,QAAQ,EAAwC,MAAM,UAAU,CAAC;AAI/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,CA+K1E"}
package/dist/rest.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { InvalidResponseError, createApiError } from "./errors.js";
2
2
  import { readProblem } from "./problem.js";
3
+ import { createPresignedUploads } from "./uploads/index.js";
3
4
  import { ActionResult, ActionRun, Definition, PROBLEM_CONTENT_TYPE, toSearchParams, validate } from "@panneau/protocol";
4
5
  //#region src/rest.ts
5
6
  var ACCEPT = `application/json, ${PROBLEM_CONTENT_TYPE}`;
@@ -149,43 +150,14 @@ function createRestAdapter(options) {
149
150
  signal
150
151
  });
151
152
  },
152
- listMedia(query = {}, { signal } = {}) {
153
- return json({
154
- path: "/media",
155
- query: toSearchParams(query),
156
- signal
157
- });
158
- },
159
- getMedia(id, { signal } = {}) {
160
- return data({
161
- path: `/media/${encode(id)}`,
162
- signal
163
- });
164
- },
165
- uploadMedia(file, values = {}, { signal } = {}) {
166
- return data({
167
- method: "POST",
168
- path: "/media",
169
- body: {
170
- ...values,
171
- file
172
- },
173
- signal
174
- });
175
- },
176
- updateMedia(id, values, { signal } = {}) {
177
- return data({
178
- method: "PATCH",
179
- path: `/media/${encode(id)}`,
180
- body: values,
181
- signal
182
- });
183
- },
184
- async deleteMedia(id, { signal } = {}) {
185
- await send({
186
- method: "DELETE",
187
- path: `/media/${encode(id)}`,
188
- signal
153
+ presignedUploads({ endpoint } = {}) {
154
+ return createPresignedUploads({
155
+ baseUrl,
156
+ ...endpoint !== void 0 && { endpoint },
157
+ fetch: fetcher,
158
+ ...options.headers !== void 0 && { headers: options.headers },
159
+ credentials: options.credentials ?? "same-origin",
160
+ createError: (problem, response) => options.createError?.(problem, response) ?? createApiError(problem)
189
161
  });
190
162
  },
191
163
  getSession({ signal } = {}) {
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 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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"}
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 { Item, ListResponse, SearchResponse, Session } from '@panneau/protocol';\n\nimport type { ActionResponse, DataAdapter } from './adapter';\nimport { type ApiError, InvalidResponseError, createApiError } from './errors';\nimport { readProblem } from './problem';\nimport { createPresignedUploads } from './uploads';\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 presignedUploads({ endpoint } = {}) {\n return createPresignedUploads({\n baseUrl,\n ...(endpoint !== undefined && { endpoint }),\n fetch: fetcher,\n ...(options.headers !== undefined && { headers: options.headers }),\n // The default of `fetch`, as the adapter's other requests\n credentials: options.credentials ?? 'same-origin',\n createError: (problem, response) =>\n options.createError?.(problem, response) ?? createApiError(problem),\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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"}
@@ -0,0 +1,58 @@
1
+ import type { Problem, Upload, UploadSignRequest } from '@panneau/protocol';
2
+ import { type ApiError } from '../errors';
3
+ /** A request to sign, as `signRequest` of `@uppy/aws-s3` 6 gives it. */
4
+ export type PresignableRequest = UploadSignRequest;
5
+ /** A signed request, as `signRequest` of `@uppy/aws-s3` 6 takes it. */
6
+ export interface PresignedResponse {
7
+ url: string;
8
+ /** The key the backend chose for a new upload. */
9
+ key?: string;
10
+ /** Headers the URL was signed with, which the browser sends. */
11
+ headers?: Record<string, string>;
12
+ }
13
+ export interface PresignedUploadsOptions {
14
+ /** URL of the API, as `api.baseUrl` in the definition: the routes are under `/uploads`. */
15
+ baseUrl?: string;
16
+ /** The `endpoint` of the definition's `uploads`, when the routes are elsewhere. */
17
+ endpoint?: string;
18
+ /** The `fetch` function to use. Defaults to the global one. */
19
+ fetch?: (input: string, init: RequestInit) => Promise<Response>;
20
+ /**
21
+ * Headers of every request, or a function that gives them: `() => getLaravelHeaders()` for
22
+ * a Laravel backend with session authentication.
23
+ */
24
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
25
+ /** Whether requests send cookies. Defaults to `include`. */
26
+ credentials?: RequestCredentials;
27
+ /** Creates the error of a failed response: `createLaravelError` for Laravel. */
28
+ createError?: (problem: Problem, response: Response) => ApiError;
29
+ }
30
+ export interface PresignedUploads {
31
+ /** Signs a request of Uppy's S3 client: pass it as `signRequest` to `@uppy/aws-s3`. */
32
+ signRequest: (request: PresignableRequest) => Promise<PresignedResponse>;
33
+ /**
34
+ * Tells the backend that the upload of a key is complete, once Uppy reports it
35
+ * (`upload-success`, with the key in `response.body.key`), and resolves with the upload.
36
+ * Values reference it as `{ upload: upload.id }`: a file field, an import, or
37
+ * the `file` of a media (`create('media', { file: { upload: upload.id } })`).
38
+ */
39
+ complete: (key: string, options?: {
40
+ name?: string;
41
+ }) => Promise<Upload>;
42
+ }
43
+ /**
44
+ * Direct uploads to S3-compatible storage with URLs that the backend signs, the `presigned` mode
45
+ * of the protocol (decision 0025): the browser sends files itself, in parts for large ones, then
46
+ * completes each upload, which values reference.
47
+ *
48
+ * ```ts
49
+ * const uploads = createPresignedUploads({ baseUrl: definition.api.baseUrl, headers: () => getLaravelHeaders() });
50
+ * uppy.use(AwsS3, { signRequest: uploads.signRequest });
51
+ * uppy.on('upload-success', async (file, response) => {
52
+ * const upload = await uploads.complete(response.body.key, { name: file.name });
53
+ * const media = await adapter.create('media', { file: { upload: upload.id } });
54
+ * });
55
+ * ```
56
+ */
57
+ export declare function createPresignedUploads({ baseUrl, endpoint, fetch: fetcher, headers, credentials, createError, }: PresignedUploadsOptions): PresignedUploads;
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/uploads/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAsB,MAAM,mBAAmB,CAAC;AAEhG,OAAO,EAAE,KAAK,QAAQ,EAAwC,MAAM,WAAW,CAAC;AAGhF,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACpC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,4DAA4D;IAC5D,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,gFAAgF;IAChF,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;CACpE;AAED,MAAM,WAAW,gBAAgB;IAC7B,uFAAuF;IACvF,WAAW,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzE;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,EACnC,OAAO,EACP,QAAQ,EACR,KAAK,EAAE,OAAwD,EAC/D,OAAO,EACP,WAAuB,EACvB,WAAkD,GACrD,EAAE,uBAAuB,GAAG,gBAAgB,CAwD5C"}
@@ -0,0 +1,61 @@
1
+ import { InvalidResponseError, createApiError } from "../errors.js";
2
+ import { readProblem } from "../problem.js";
3
+ //#region src/uploads/index.ts
4
+ /**
5
+ * Direct uploads to S3-compatible storage with URLs that the backend signs, the `presigned` mode
6
+ * of the protocol (decision 0025): the browser sends files itself, in parts for large ones, then
7
+ * completes each upload, which values reference.
8
+ *
9
+ * ```ts
10
+ * const uploads = createPresignedUploads({ baseUrl: definition.api.baseUrl, headers: () => getLaravelHeaders() });
11
+ * uppy.use(AwsS3, { signRequest: uploads.signRequest });
12
+ * uppy.on('upload-success', async (file, response) => {
13
+ * const upload = await uploads.complete(response.body.key, { name: file.name });
14
+ * const media = await adapter.create('media', { file: { upload: upload.id } });
15
+ * });
16
+ * ```
17
+ */
18
+ function createPresignedUploads({ baseUrl, endpoint, fetch: fetcher = (input, init) => globalThis.fetch(input, init), headers, credentials = "include", createError = (problem) => createApiError(problem) }) {
19
+ const base = (endpoint ?? `${(baseUrl ?? "").replace(/\/+$/, "")}/uploads`).replace(/\/+$/, "");
20
+ const uploads = /* @__PURE__ */ new Map();
21
+ const post = async (path, body) => {
22
+ const requestHeaders = new Headers(typeof headers === "function" ? await headers() : headers);
23
+ requestHeaders.set("Accept", "application/json");
24
+ const init = {
25
+ method: "POST",
26
+ headers: requestHeaders,
27
+ credentials
28
+ };
29
+ if (body !== void 0) {
30
+ requestHeaders.set("Content-Type", "application/json");
31
+ init.body = JSON.stringify(body);
32
+ }
33
+ const response = await fetcher(`${base}${path}`, init);
34
+ if (!response.ok) throw createError(await readProblem(response), response);
35
+ return response.json();
36
+ };
37
+ return {
38
+ async signRequest(request) {
39
+ const body = await post("/sign", request);
40
+ if (typeof body?.url !== "string") throw new InvalidResponseError("The signed request has no URL.", []);
41
+ const key = body.key ?? request.key;
42
+ if (typeof body.upload === "string") uploads.set(key, body.upload);
43
+ return {
44
+ url: body.url,
45
+ ...body.key !== void 0 && { key: body.key },
46
+ ...body.headers !== void 0 && { headers: body.headers }
47
+ };
48
+ },
49
+ async complete(key, { name } = {}) {
50
+ const upload = uploads.get(key) ?? key.split("/").pop() ?? key;
51
+ const body = await post(`/${encodeURIComponent(upload)}/complete`, name !== void 0 ? { name } : void 0);
52
+ if (typeof body?.data?.id !== "string") throw new InvalidResponseError("The completed upload has no id.", []);
53
+ uploads.delete(key);
54
+ return body.data;
55
+ }
56
+ };
57
+ }
58
+ //#endregion
59
+ export { createPresignedUploads };
60
+
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/uploads/index.ts"],"sourcesContent":["import type { Problem, Upload, UploadSignRequest, UploadSignResponse } from '@panneau/protocol';\n\nimport { type ApiError, InvalidResponseError, createApiError } from '../errors';\nimport { readProblem } from '../problem';\n\n/** A request to sign, as `signRequest` of `@uppy/aws-s3` 6 gives it. */\nexport type PresignableRequest = UploadSignRequest;\n\n/** A signed request, as `signRequest` of `@uppy/aws-s3` 6 takes it. */\nexport interface PresignedResponse {\n url: string;\n /** The key the backend chose for a new upload. */\n key?: string;\n /** Headers the URL was signed with, which the browser sends. */\n headers?: Record<string, string>;\n}\n\nexport interface PresignedUploadsOptions {\n /** URL of the API, as `api.baseUrl` in the definition: the routes are under `/uploads`. */\n baseUrl?: string;\n /** The `endpoint` of the definition's `uploads`, when the routes are elsewhere. */\n endpoint?: string;\n /** The `fetch` function to use. Defaults to the global one. */\n fetch?: (input: string, init: RequestInit) => Promise<Response>;\n /**\n * Headers of every request, or a function that gives them: `() => getLaravelHeaders()` for\n * a Laravel backend with session authentication.\n */\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n /** Whether requests send cookies. Defaults to `include`. */\n credentials?: RequestCredentials;\n /** Creates the error of a failed response: `createLaravelError` for Laravel. */\n createError?: (problem: Problem, response: Response) => ApiError;\n}\n\nexport interface PresignedUploads {\n /** Signs a request of Uppy's S3 client: pass it as `signRequest` to `@uppy/aws-s3`. */\n signRequest: (request: PresignableRequest) => Promise<PresignedResponse>;\n /**\n * Tells the backend that the upload of a key is complete, once Uppy reports it\n * (`upload-success`, with the key in `response.body.key`), and resolves with the upload.\n * Values reference it as `{ upload: upload.id }`: a file field, an import, or\n * the `file` of a media (`create('media', { file: { upload: upload.id } })`).\n */\n complete: (key: string, options?: { name?: string }) => Promise<Upload>;\n}\n\n/**\n * Direct uploads to S3-compatible storage with URLs that the backend signs, the `presigned` mode\n * of the protocol (decision 0025): the browser sends files itself, in parts for large ones, then\n * completes each upload, which values reference.\n *\n * ```ts\n * const uploads = createPresignedUploads({ baseUrl: definition.api.baseUrl, headers: () => getLaravelHeaders() });\n * uppy.use(AwsS3, { signRequest: uploads.signRequest });\n * uppy.on('upload-success', async (file, response) => {\n * const upload = await uploads.complete(response.body.key, { name: file.name });\n * const media = await adapter.create('media', { file: { upload: upload.id } });\n * });\n * ```\n */\nexport function createPresignedUploads({\n baseUrl,\n endpoint,\n fetch: fetcher = (input, init) => globalThis.fetch(input, init),\n headers,\n credentials = 'include',\n createError = (problem) => createApiError(problem),\n}: PresignedUploadsOptions): PresignedUploads {\n const base = (endpoint ?? `${(baseUrl ?? '').replace(/\\/+$/, '')}/uploads`).replace(/\\/+$/, '');\n // The upload of each key, from the response that created it.\n const uploads = new Map<string, string>();\n\n const post = async (path: string, body?: unknown): Promise<unknown> => {\n const requestHeaders = new Headers(\n typeof headers === 'function' ? await headers() : headers,\n );\n requestHeaders.set('Accept', 'application/json');\n const init: RequestInit = { method: 'POST', headers: requestHeaders, credentials };\n if (body !== undefined) {\n requestHeaders.set('Content-Type', 'application/json');\n init.body = JSON.stringify(body);\n }\n const response = await fetcher(`${base}${path}`, init);\n if (!response.ok) {\n throw createError(await readProblem(response), response);\n }\n return response.json();\n };\n\n return {\n async signRequest(request) {\n const body = (await post('/sign', request)) as UploadSignResponse;\n if (typeof body?.url !== 'string') {\n throw new InvalidResponseError('The signed request has no URL.', []);\n }\n const key = body.key ?? request.key;\n if (typeof body.upload === 'string') {\n uploads.set(key, body.upload);\n }\n return {\n url: body.url,\n ...(body.key !== undefined && { key: body.key }),\n ...(body.headers !== undefined && { headers: body.headers }),\n };\n },\n\n async complete(key, { name } = {}) {\n // The backend names the upload in the sign response; without it, it is the last\n // segment of the key it chose (`uploads/upl_01H…`).\n const upload = uploads.get(key) ?? key.split('/').pop() ?? key;\n const body = (await post(\n `/${encodeURIComponent(upload)}/complete`,\n name !== undefined ? { name } : undefined,\n )) as {\n data?: Upload;\n };\n if (typeof body?.data?.id !== 'string') {\n throw new InvalidResponseError('The completed upload has no id.', []);\n }\n uploads.delete(key);\n return body.data;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA6DA,SAAgB,uBAAuB,EACnC,SACA,UACA,OAAO,WAAW,OAAO,SAAS,WAAW,MAAM,OAAO,IAAI,GAC9D,SACA,cAAc,WACd,eAAe,YAAY,eAAe,OAAO,KACP;CAC1C,MAAM,QAAQ,YAAY,IAAI,WAAW,GAAA,CAAI,QAAQ,QAAQ,EAAE,EAAC,UAAA,CAAY,QAAQ,QAAQ,EAAE;CAE9F,MAAM,0BAAU,IAAI,IAAoB;CAExC,MAAM,OAAO,OAAO,MAAc,SAAqC;EACnE,MAAM,iBAAiB,IAAI,QACvB,OAAO,YAAY,aAAa,MAAM,QAAQ,IAAI,OACtD;EACA,eAAe,IAAI,UAAU,kBAAkB;EAC/C,MAAM,OAAoB;GAAE,QAAQ;GAAQ,SAAS;GAAgB;EAAY;EACjF,IAAI,SAAS,KAAA,GAAW;GACpB,eAAe,IAAI,gBAAgB,kBAAkB;GACrD,KAAK,OAAO,KAAK,UAAU,IAAI;EACnC;EACA,MAAM,WAAW,MAAM,QAAQ,GAAG,OAAO,QAAQ,IAAI;EACrD,IAAI,CAAC,SAAS,IACV,MAAM,YAAY,MAAM,YAAY,QAAQ,GAAG,QAAQ;EAE3D,OAAO,SAAS,KAAK;CACzB;CAEA,OAAO;EACH,MAAM,YAAY,SAAS;GACvB,MAAM,OAAQ,MAAM,KAAK,SAAS,OAAO;GACzC,IAAI,OAAO,MAAM,QAAQ,UACrB,MAAM,IAAI,qBAAqB,kCAAkC,CAAA,CAAE;GAEvE,MAAM,MAAM,KAAK,OAAO,QAAQ;GAChC,IAAI,OAAO,KAAK,WAAW,UACvB,QAAQ,IAAI,KAAK,KAAK,MAAM;GAEhC,OAAO;IACH,KAAK,KAAK;IACV,GAAI,KAAK,QAAQ,KAAA,KAAa,EAAE,KAAK,KAAK,IAAI;IAC9C,GAAI,KAAK,YAAY,KAAA,KAAa,EAAE,SAAS,KAAK,QAAQ;GAC9D;EACJ;EAEA,MAAM,SAAS,KAAK,EAAE,SAAS,CAAC,GAAG;GAG/B,MAAM,SAAS,QAAQ,IAAI,GAAG,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAC3D,MAAM,OAAQ,MAAM,KAChB,IAAI,mBAAmB,MAAM,EAAC,YAC9B,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,KAAA,CACpC;GAGA,IAAI,OAAO,MAAM,MAAM,OAAO,UAC1B,MAAM,IAAI,qBAAqB,mCAAmC,CAAA,CAAE;GAExE,QAAQ,OAAO,GAAG;GAClB,OAAO,KAAK;EAChB;CACJ;AACJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panneau/data",
3
- "version": "5.0.0-alpha.3",
3
+ "version": "5.0.0-alpha.4",
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",
@@ -26,6 +26,11 @@
26
26
  "types": "./dist/query/index.d.ts",
27
27
  "default": "./dist/query/index.js"
28
28
  },
29
+ "./uploads": {
30
+ "@panneau/source": "./src/uploads/index.ts",
31
+ "types": "./dist/uploads/index.d.ts",
32
+ "default": "./dist/uploads/index.js"
33
+ },
29
34
  "./package.json": "./package.json"
30
35
  },
31
36
  "files": [
@@ -44,7 +49,7 @@
44
49
  "react": "^19.2.0"
45
50
  },
46
51
  "dependencies": {
47
- "@panneau/protocol": "5.0.0-alpha.3"
52
+ "@panneau/protocol": "5.0.0-alpha.4"
48
53
  },
49
54
  "peerDependenciesMeta": {
50
55
  "@tanstack/react-query": {
@@ -55,6 +60,6 @@
55
60
  }
56
61
  },
57
62
  "devDependencies": {
58
- "@panneau/core": "5.0.0-alpha.3"
63
+ "@panneau/core": "5.0.0-alpha.4"
59
64
  }
60
65
  }