@orthacms/webhooks-admin 0.5.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +9 -0
  6. package/dist/lib/application/useContentTypeOptions/index.d.ts +18 -0
  7. package/dist/lib/application/useContentTypeOptions/index.d.ts.map +1 -0
  8. package/dist/lib/application/useContentTypeOptions/index.js +40 -0
  9. package/dist/lib/application/useWebhookDeliveries/index.d.ts +19 -0
  10. package/dist/lib/application/useWebhookDeliveries/index.d.ts.map +1 -0
  11. package/dist/lib/application/useWebhookDeliveries/index.js +48 -0
  12. package/dist/lib/application/useWebhookEndpoints/index.d.ts +12 -0
  13. package/dist/lib/application/useWebhookEndpoints/index.d.ts.map +1 -0
  14. package/dist/lib/application/useWebhookEndpoints/index.js +26 -0
  15. package/dist/lib/application/useWebhookEvents/index.d.ts +9 -0
  16. package/dist/lib/application/useWebhookEvents/index.d.ts.map +1 -0
  17. package/dist/lib/application/useWebhookEvents/index.js +20 -0
  18. package/dist/lib/application/useWebhookMutations/index.d.ts +25 -0
  19. package/dist/lib/application/useWebhookMutations/index.d.ts.map +1 -0
  20. package/dist/lib/application/useWebhookMutations/index.js +79 -0
  21. package/dist/lib/application/useWorkspaceOptions/index.d.ts +9 -0
  22. package/dist/lib/application/useWorkspaceOptions/index.d.ts.map +1 -0
  23. package/dist/lib/application/useWorkspaceOptions/index.js +19 -0
  24. package/dist/lib/domain/contentTypeChoices/index.d.ts +65 -0
  25. package/dist/lib/domain/contentTypeChoices/index.d.ts.map +1 -0
  26. package/dist/lib/domain/contentTypeChoices/index.js +70 -0
  27. package/dist/lib/domain/headerRules/index.d.ts +37 -0
  28. package/dist/lib/domain/headerRules/index.d.ts.map +1 -0
  29. package/dist/lib/domain/headerRules/index.js +66 -0
  30. package/dist/lib/domain/types/contentTypeOption/index.d.ts +14 -0
  31. package/dist/lib/domain/types/contentTypeOption/index.d.ts.map +1 -0
  32. package/dist/lib/domain/types/contentTypeOption/index.js +1 -0
  33. package/dist/lib/domain/types/webhook/index.d.ts +102 -0
  34. package/dist/lib/domain/types/webhook/index.d.ts.map +1 -0
  35. package/dist/lib/domain/types/webhook/index.js +18 -0
  36. package/dist/lib/domain/types/workspaceOption/index.d.ts +16 -0
  37. package/dist/lib/domain/types/workspaceOption/index.d.ts.map +1 -0
  38. package/dist/lib/domain/types/workspaceOption/index.js +1 -0
  39. package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts +11 -0
  40. package/dist/lib/infrastructure/httpWebhookGateway/index.d.ts.map +1 -0
  41. package/dist/lib/infrastructure/httpWebhookGateway/index.js +138 -0
  42. package/dist/lib/infrastructure/serverMessageOf/index.d.ts +16 -0
  43. package/dist/lib/infrastructure/serverMessageOf/index.d.ts.map +1 -0
  44. package/dist/lib/infrastructure/serverMessageOf/index.js +24 -0
  45. package/dist/lib/infrastructure/webhookGateway/index.d.ts +62 -0
  46. package/dist/lib/infrastructure/webhookGateway/index.d.ts.map +1 -0
  47. package/dist/lib/infrastructure/webhookGateway/index.js +1 -0
  48. package/dist/lib/infrastructure/webhookMapper/index.d.ts +104 -0
  49. package/dist/lib/infrastructure/webhookMapper/index.d.ts.map +1 -0
  50. package/dist/lib/infrastructure/webhookMapper/index.js +49 -0
  51. package/dist/lib/infrastructure/webhooksKeys/index.d.ts +35 -0
  52. package/dist/lib/infrastructure/webhooksKeys/index.d.ts.map +1 -0
  53. package/dist/lib/infrastructure/webhooksKeys/index.js +23 -0
  54. package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts +6 -0
  55. package/dist/lib/presentation/components/DeliveryStatusBadge/index.d.ts.map +1 -0
  56. package/dist/lib/presentation/components/DeliveryStatusBadge/index.js +30 -0
  57. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts +25 -0
  58. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.d.ts.map +1 -0
  59. package/dist/lib/presentation/components/RevealWebhookSecretDialog/index.js +107 -0
  60. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts +19 -0
  61. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.d.ts.map +1 -0
  62. package/dist/lib/presentation/components/WebhookDeliveriesPanel/DeliveryDetailSheet/index.js +72 -0
  63. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts +12 -0
  64. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.d.ts.map +1 -0
  65. package/dist/lib/presentation/components/WebhookDeliveriesPanel/index.js +132 -0
  66. package/dist/lib/presentation/components/WebhookForm/index.d.ts +46 -0
  67. package/dist/lib/presentation/components/WebhookForm/index.d.ts.map +1 -0
  68. package/dist/lib/presentation/components/WebhookForm/index.js +412 -0
  69. package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts +5 -0
  70. package/dist/lib/presentation/components/WebhooksEmpty/index.d.ts.map +1 -0
  71. package/dist/lib/presentation/components/WebhooksEmpty/index.js +23 -0
  72. package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts +3 -0
  73. package/dist/lib/presentation/components/WebhooksNoAccess/index.d.ts.map +1 -0
  74. package/dist/lib/presentation/components/WebhooksNoAccess/index.js +18 -0
  75. package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts +20 -0
  76. package/dist/lib/presentation/components/WebhooksSkeleton/index.d.ts.map +1 -0
  77. package/dist/lib/presentation/components/WebhooksSkeleton/index.js +45 -0
  78. package/dist/lib/presentation/components/WebhooksTable/index.d.ts +12 -0
  79. package/dist/lib/presentation/components/WebhooksTable/index.d.ts.map +1 -0
  80. package/dist/lib/presentation/components/WebhooksTable/index.js +79 -0
  81. package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts +10 -0
  82. package/dist/lib/presentation/pages/WebhookDetailPage/index.d.ts.map +1 -0
  83. package/dist/lib/presentation/pages/WebhookDetailPage/index.js +191 -0
  84. package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts +28 -0
  85. package/dist/lib/presentation/pages/WebhookEditorPage/index.d.ts.map +1 -0
  86. package/dist/lib/presentation/pages/WebhookEditorPage/index.js +149 -0
  87. package/dist/lib/presentation/pages/WebhooksPage/index.d.ts +11 -0
  88. package/dist/lib/presentation/pages/WebhooksPage/index.d.ts.map +1 -0
  89. package/dist/lib/presentation/pages/WebhooksPage/index.js +65 -0
  90. package/dist/lib/utils/webhooksPlugin/index.d.ts +35 -0
  91. package/dist/lib/utils/webhooksPlugin/index.d.ts.map +1 -0
  92. package/dist/lib/utils/webhooksPlugin/index.js +88 -0
  93. package/package.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ortha CMS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @orthacms/webhooks-admin
2
+
3
+ Part of [Ortha CMS](https://github.com/ortha-source/ortha-cms).
4
+
5
+ ```sh
6
+ npm install @orthacms/webhooks-admin
7
+ ```
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@orthacms/webhooks-admin` — the global webhooks surface.
3
+ *
4
+ * Two pages in the sidebar's directory group: the endpoint list, and one
5
+ * endpoint with its settings and its delivery log. Both are administrator-only,
6
+ * because an endpoint is not scoped to a workspace and holds a signing secret.
7
+ */
8
+ export { WebhooksPlugin, type WebhooksAdminPlugin } from './lib/utils/webhooksPlugin';
9
+ export { useWebhookEndpoint, useWebhookEndpoints } from './lib/application/useWebhookEndpoints';
10
+ export type { CreatedWebhookEndpoint, WebhookDelivery, WebhookDeliveryDetail, WebhookDeliveryPage, WebhookEndpoint, WebhookEventOption, WebhookLastDelivery, WebhookTestResult } from './lib/domain/types/webhook';
11
+ export type { SaveWebhookInput, WebhookGateway } from './lib/infrastructure/webhookGateway';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,cAAc,EACd,KAAK,mBAAmB,EAC3B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACH,kBAAkB,EAClB,mBAAmB,EACtB,MAAM,uCAAuC,CAAC;AAE/C,YAAY,EACR,sBAAsB,EACtB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACR,gBAAgB,EAChB,cAAc,EACjB,MAAM,qCAAqC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@orthacms/webhooks-admin` — the global webhooks surface.
3
+ *
4
+ * Two pages in the sidebar's directory group: the endpoint list, and one
5
+ * endpoint with its settings and its delivery log. Both are administrator-only,
6
+ * because an endpoint is not scoped to a workspace and holds a signing secret.
7
+ */
8
+ export { WebhooksPlugin } from './lib/utils/webhooksPlugin';
9
+ export { useWebhookEndpoint, useWebhookEndpoints } from './lib/application/useWebhookEndpoints';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The code-defined content types, for the endpoint editor's type picker.
3
+ *
4
+ * Two things make this query unlike the others here.
5
+ *
6
+ * It reads content's registry (`GET /api/content-schema`), which is gated on
7
+ * `content:read` rather than on a webhooks permission. Every administrator
8
+ * holds it, so in practice it is there — but a deployment is free to define a
9
+ * role that manages webhooks without reading content, and a **403 is answered
10
+ * with an empty catalogue rather than an error**: the picker is a convenience
11
+ * over a free-text filter, and typing a machine name still works with no
12
+ * catalogue at all. Any other failure is a real failure and still throws.
13
+ *
14
+ * Like the other pickers it only exists inside a dialog, so the caller passes
15
+ * `enabled` and nothing is fetched before the dialog opens.
16
+ */
17
+ export declare function useContentTypeOptions(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../domain/types/contentTypeOption").ContentTypeOption[]>, Error>;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useContentTypeOptions/index.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,UAAO,sIAgBnD"}
@@ -0,0 +1,40 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { ApiError } from '@orthacms/utils-admin';
3
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
4
+ import { webhooksKeys } from '../../infrastructure/webhooksKeys';
5
+ /** How long the type catalogue is treated as fresh inside the editor. */
6
+ const OPTIONS_STALE_TIME_MS = 60_000;
7
+ /**
8
+ * The code-defined content types, for the endpoint editor's type picker.
9
+ *
10
+ * Two things make this query unlike the others here.
11
+ *
12
+ * It reads content's registry (`GET /api/content-schema`), which is gated on
13
+ * `content:read` rather than on a webhooks permission. Every administrator
14
+ * holds it, so in practice it is there — but a deployment is free to define a
15
+ * role that manages webhooks without reading content, and a **403 is answered
16
+ * with an empty catalogue rather than an error**: the picker is a convenience
17
+ * over a free-text filter, and typing a machine name still works with no
18
+ * catalogue at all. Any other failure is a real failure and still throws.
19
+ *
20
+ * Like the other pickers it only exists inside a dialog, so the caller passes
21
+ * `enabled` and nothing is fetched before the dialog opens.
22
+ */
23
+ export function useContentTypeOptions(enabled = true) {
24
+ return useQuery({
25
+ queryKey: webhooksKeys.contentTypes(),
26
+ queryFn: async () => {
27
+ try {
28
+ return await httpWebhookGateway.listContentTypeOptions();
29
+ }
30
+ catch (error) {
31
+ if (error instanceof ApiError && error.status === 403) {
32
+ return [];
33
+ }
34
+ throw error;
35
+ }
36
+ },
37
+ staleTime: OPTIONS_STALE_TIME_MS,
38
+ enabled
39
+ });
40
+ }
@@ -0,0 +1,19 @@
1
+ import { type DeliveriesListParams } from '../../infrastructure/webhooksKeys';
2
+ /** Rows per page in the delivery log. */
3
+ export declare const DEFAULT_PAGE_SIZE = 25;
4
+ /**
5
+ * One page of an endpoint's delivery log.
6
+ *
7
+ * It **polls while anything is unfinished** and stops when nothing is: a
8
+ * delivery that is queued or waiting for its retry changes state without
9
+ * anybody pressing anything, and a log that needs a manual refresh to show that
10
+ * is a log people stop trusting. Once every row is terminal there is nothing
11
+ * left to watch, so the polling stops rather than running for as long as the
12
+ * tab is open.
13
+ *
14
+ * `keepPreviousData` avoids the page blanking between pages and between polls.
15
+ */
16
+ export declare function useWebhookDeliveries(endpointId: string, params: DeliveriesListParams, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../..").WebhookDeliveryPage>, Error>;
17
+ /** One delivery with its request and response bodies, for the detail panel. */
18
+ export declare function useWebhookDelivery(endpointId: string, deliveryId: string | null): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../..").WebhookDeliveryDetail>, Error>;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useWebhookDeliveries/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,oBAAoB,EAC5B,MAAM,mCAAmC,CAAC;AAE3C,yCAAyC;AACzC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAKpC;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,oBAAoB,EAC5B,OAAO,UAAO,0GAmBjB;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAAG,IAAI,4GAY5B"}
@@ -0,0 +1,48 @@
1
+ import { keepPreviousData, useQuery } from '@tanstack/react-query';
2
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
3
+ import { webhooksKeys } from '../../infrastructure/webhooksKeys';
4
+ /** Rows per page in the delivery log. */
5
+ export const DEFAULT_PAGE_SIZE = 25;
6
+ /** How often the log refreshes while something in it is still in flight. */
7
+ const IN_FLIGHT_POLL_MS = 3_000;
8
+ /**
9
+ * One page of an endpoint's delivery log.
10
+ *
11
+ * It **polls while anything is unfinished** and stops when nothing is: a
12
+ * delivery that is queued or waiting for its retry changes state without
13
+ * anybody pressing anything, and a log that needs a manual refresh to show that
14
+ * is a log people stop trusting. Once every row is terminal there is nothing
15
+ * left to watch, so the polling stops rather than running for as long as the
16
+ * tab is open.
17
+ *
18
+ * `keepPreviousData` avoids the page blanking between pages and between polls.
19
+ */
20
+ export function useWebhookDeliveries(endpointId, params, enabled = true) {
21
+ return useQuery({
22
+ queryKey: webhooksKeys.deliveries(endpointId, params),
23
+ queryFn: () => httpWebhookGateway.listDeliveries(endpointId, params),
24
+ placeholderData: keepPreviousData,
25
+ refetchInterval: (query) => {
26
+ const page = query.state.data;
27
+ if (!page)
28
+ return false;
29
+ const inFlight = page.items.some((delivery) => delivery.status === 'pending' ||
30
+ delivery.status === 'delivering' ||
31
+ delivery.status === 'failed');
32
+ return inFlight ? IN_FLIGHT_POLL_MS : false;
33
+ },
34
+ enabled
35
+ });
36
+ }
37
+ /** One delivery with its request and response bodies, for the detail panel. */
38
+ export function useWebhookDelivery(endpointId, deliveryId) {
39
+ return useQuery({
40
+ queryKey: [
41
+ ...webhooksKeys.deliveriesOf(endpointId),
42
+ 'detail',
43
+ deliveryId
44
+ ],
45
+ queryFn: () => httpWebhookGateway.getDelivery(endpointId, deliveryId),
46
+ enabled: deliveryId !== null
47
+ });
48
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Every configured endpoint.
3
+ *
4
+ * Not paginated: this is deployment configuration, and an installation with
5
+ * enough endpoints to need pages has a different problem. `enabled` is how the
6
+ * caller withholds the request until `webhooks:read` is confirmed, so an
7
+ * unauthorized page never fires a request the server would 403.
8
+ */
9
+ export declare function useWebhookEndpoints(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../..").WebhookEndpoint[]>, Error>;
10
+ /** One endpoint, for the detail page. */
11
+ export declare function useWebhookEndpoint(id: string, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../..").WebhookEndpoint>, Error>;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useWebhookEndpoints/index.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,UAAO,wGAMjD;AAED,yCAAyC;AACzC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,UAAO,sGAM5D"}
@@ -0,0 +1,26 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
3
+ import { webhooksKeys } from '../../infrastructure/webhooksKeys';
4
+ /**
5
+ * Every configured endpoint.
6
+ *
7
+ * Not paginated: this is deployment configuration, and an installation with
8
+ * enough endpoints to need pages has a different problem. `enabled` is how the
9
+ * caller withholds the request until `webhooks:read` is confirmed, so an
10
+ * unauthorized page never fires a request the server would 403.
11
+ */
12
+ export function useWebhookEndpoints(enabled = true) {
13
+ return useQuery({
14
+ queryKey: webhooksKeys.list(),
15
+ queryFn: () => httpWebhookGateway.list(),
16
+ enabled
17
+ });
18
+ }
19
+ /** One endpoint, for the detail page. */
20
+ export function useWebhookEndpoint(id, enabled = true) {
21
+ return useQuery({
22
+ queryKey: webhooksKeys.detail(id),
23
+ queryFn: () => httpWebhookGateway.get(id),
24
+ enabled
25
+ });
26
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The subscribable event catalogue.
3
+ *
4
+ * Served by the API rather than compiled into the SPA, so an admin running
5
+ * against a newer server offers the kinds that server actually knows about —
6
+ * the same reason the content-type picker reads `/content-schema`.
7
+ */
8
+ export declare function useWebhookEvents(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../..").WebhookEventOption[]>, Error>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useWebhookEvents/index.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,UAAO,2GAO9C"}
@@ -0,0 +1,20 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
3
+ import { webhooksKeys } from '../../infrastructure/webhooksKeys';
4
+ /** How long the catalogue is treated as fresh — it changes on deploy, not in use. */
5
+ const CATALOGUE_STALE_TIME_MS = 5 * 60_000;
6
+ /**
7
+ * The subscribable event catalogue.
8
+ *
9
+ * Served by the API rather than compiled into the SPA, so an admin running
10
+ * against a newer server offers the kinds that server actually knows about —
11
+ * the same reason the content-type picker reads `/content-schema`.
12
+ */
13
+ export function useWebhookEvents(enabled = true) {
14
+ return useQuery({
15
+ queryKey: webhooksKeys.events(),
16
+ queryFn: () => httpWebhookGateway.listEvents(),
17
+ staleTime: CATALOGUE_STALE_TIME_MS,
18
+ enabled
19
+ });
20
+ }
@@ -0,0 +1,25 @@
1
+ import type { SaveWebhookInput } from '../../infrastructure/webhookGateway';
2
+ /** Creates an endpoint; the result carries the one-time signing secret. */
3
+ export declare function useCreateWebhook(): import("@tanstack/react-query").UseMutationResult<import("../../..").CreatedWebhookEndpoint, Error, SaveWebhookInput, unknown>;
4
+ /** Updates an endpoint's configuration. */
5
+ export declare function useUpdateWebhook(): import("@tanstack/react-query").UseMutationResult<import("../../..").WebhookEndpoint, Error, {
6
+ id: string;
7
+ input: SaveWebhookInput;
8
+ }, unknown>;
9
+ /** Deletes an endpoint and, with it, its delivery log. */
10
+ export declare function useDeleteWebhook(): import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
11
+ /** Mints a new signing secret and returns it once. */
12
+ export declare function useRotateWebhookSecret(): import("@tanstack/react-query").UseMutationResult<import("../../..").CreatedWebhookEndpoint, Error, string, unknown>;
13
+ /**
14
+ * Sends a test ping.
15
+ *
16
+ * Nothing is invalidated: a test is not queued and never becomes a log row, so
17
+ * there is no cached state it could have changed.
18
+ */
19
+ export declare function useTestWebhook(): import("@tanstack/react-query").UseMutationResult<import("../../..").WebhookTestResult, Error, string, unknown>;
20
+ /** Queues an already-sent delivery again. */
21
+ export declare function useRedeliverWebhook(): import("@tanstack/react-query").UseMutationResult<import("../../..").WebhookDelivery, Error, {
22
+ endpointId: string;
23
+ deliveryId: string;
24
+ }, unknown>;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useWebhookMutations/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,2EAA2E;AAC3E,wBAAgB,gBAAgB,mIAS/B;AAED,2CAA2C;AAC3C,wBAAgB,gBAAgB;QAGU,MAAM;WAAS,gBAAgB;YAaxE;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,oFAY/B;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,yHAYrC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,oHAI7B;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB;gBAOX,MAAM;gBACN,MAAM;YAY7B"}
@@ -0,0 +1,79 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
3
+ import { webhooksKeys } from '../../infrastructure/webhooksKeys';
4
+ /** Creates an endpoint; the result carries the one-time signing secret. */
5
+ export function useCreateWebhook() {
6
+ const queryClient = useQueryClient();
7
+ return useMutation({
8
+ mutationFn: (input) => httpWebhookGateway.create(input),
9
+ onSuccess: () => {
10
+ queryClient.invalidateQueries({ queryKey: webhooksKeys.list() });
11
+ }
12
+ });
13
+ }
14
+ /** Updates an endpoint's configuration. */
15
+ export function useUpdateWebhook() {
16
+ const queryClient = useQueryClient();
17
+ return useMutation({
18
+ mutationFn: ({ id, input }) => httpWebhookGateway.update(id, input),
19
+ onSuccess: (endpoint) => {
20
+ // The response is the updated row, so the detail page can be
21
+ // written directly rather than refetched; the list still needs
22
+ // invalidating because the change may reorder or re-badge it.
23
+ queryClient.setQueryData(webhooksKeys.detail(endpoint.id), endpoint);
24
+ queryClient.invalidateQueries({ queryKey: webhooksKeys.list() });
25
+ }
26
+ });
27
+ }
28
+ /** Deletes an endpoint and, with it, its delivery log. */
29
+ export function useDeleteWebhook() {
30
+ const queryClient = useQueryClient();
31
+ return useMutation({
32
+ mutationFn: (id) => httpWebhookGateway.remove(id),
33
+ onSuccess: (_result, id) => {
34
+ queryClient.removeQueries({ queryKey: webhooksKeys.detail(id) });
35
+ queryClient.removeQueries({
36
+ queryKey: webhooksKeys.deliveriesOf(id)
37
+ });
38
+ queryClient.invalidateQueries({ queryKey: webhooksKeys.list() });
39
+ }
40
+ });
41
+ }
42
+ /** Mints a new signing secret and returns it once. */
43
+ export function useRotateWebhookSecret() {
44
+ const queryClient = useQueryClient();
45
+ return useMutation({
46
+ mutationFn: (id) => httpWebhookGateway.rotateSecret(id),
47
+ onSuccess: ({ endpoint }) => {
48
+ queryClient.setQueryData(webhooksKeys.detail(endpoint.id), endpoint);
49
+ queryClient.invalidateQueries({ queryKey: webhooksKeys.list() });
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * Sends a test ping.
55
+ *
56
+ * Nothing is invalidated: a test is not queued and never becomes a log row, so
57
+ * there is no cached state it could have changed.
58
+ */
59
+ export function useTestWebhook() {
60
+ return useMutation({
61
+ mutationFn: (id) => httpWebhookGateway.test(id)
62
+ });
63
+ }
64
+ /** Queues an already-sent delivery again. */
65
+ export function useRedeliverWebhook() {
66
+ const queryClient = useQueryClient();
67
+ return useMutation({
68
+ mutationFn: ({ endpointId, deliveryId }) => httpWebhookGateway.redeliver(endpointId, deliveryId),
69
+ onSuccess: (_delivery, { endpointId }) => {
70
+ // Scoped to this endpoint's log rather than the whole root: the log
71
+ // is polled, and invalidating everything would refetch every other
72
+ // endpoint's pages for a change that cannot affect them.
73
+ queryClient.invalidateQueries({
74
+ queryKey: webhooksKeys.deliveriesOf(endpointId)
75
+ });
76
+ queryClient.invalidateQueries({ queryKey: webhooksKeys.list() });
77
+ }
78
+ });
79
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Every workspace, for the endpoint editor's picker.
3
+ *
4
+ * Requires `workspaces:read`, which every role holds — but the caller still
5
+ * passes `enabled`, because the picker only exists inside a dialog and there is
6
+ * no reason to fetch before it opens.
7
+ */
8
+ export declare function useWorkspaceOptions(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<import("../../domain/types/workspaceOption").WorkspaceOption[]>, Error>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/application/useWorkspaceOptions/index.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,UAAO,kIAOjD"}
@@ -0,0 +1,19 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { httpWebhookGateway } from '../../infrastructure/httpWebhookGateway';
3
+ /** How long the workspace list is treated as fresh inside the editor. */
4
+ const OPTIONS_STALE_TIME_MS = 60_000;
5
+ /**
6
+ * Every workspace, for the endpoint editor's picker.
7
+ *
8
+ * Requires `workspaces:read`, which every role holds — but the caller still
9
+ * passes `enabled`, because the picker only exists inside a dialog and there is
10
+ * no reason to fetch before it opens.
11
+ */
12
+ export function useWorkspaceOptions(enabled = true) {
13
+ return useQuery({
14
+ queryKey: ['webhooks', 'workspace-options'],
15
+ queryFn: () => httpWebhookGateway.listWorkspaceOptions(),
16
+ staleTime: OPTIONS_STALE_TIME_MS,
17
+ enabled
18
+ });
19
+ }
@@ -0,0 +1,65 @@
1
+ import type { ContentTypeOption } from '../types/contentTypeOption';
2
+ import type { WorkspaceOption } from '../types/workspaceOption';
3
+ /**
4
+ * One row of the content-type picker.
5
+ *
6
+ * `known` is the whole point of this module: a subscription may name a type the
7
+ * running build does not define — one that is about to be added, or one that
8
+ * was removed, or a typo. Such a name must stay selectable and must survive a
9
+ * save, but it should not look like the types the registry vouches for.
10
+ */
11
+ export type ContentTypeChoice = {
12
+ /** The machine name — what travels on the wire. */
13
+ value: string;
14
+ /** The registry's label, or the name itself when it has none. */
15
+ label: string;
16
+ /** Whether this build defines the type. */
17
+ known: boolean;
18
+ };
19
+ /**
20
+ * The picker's options: the registry's types, plus any already-selected name it
21
+ * does not know, appended in selection order.
22
+ *
23
+ * Dropping the unknown ones would be the worst possible behaviour here — the
24
+ * picker would render a subscription it cannot express, and the next save would
25
+ * silently widen the endpoint to types it was deliberately narrowed away from.
26
+ */
27
+ export declare function contentTypeChoices(catalogue: readonly ContentTypeOption[], selected: readonly string[]): ContentTypeChoice[];
28
+ /**
29
+ * Reads typed-in machine names.
30
+ *
31
+ * Commas, whitespace and newlines all separate, because this field is pasted
32
+ * into as often as it is typed into, and `article, product` is what a paste
33
+ * looks like. The server accepts any string here — it must, or a type that does
34
+ * not exist yet could never be subscribed to — so this only trims and splits;
35
+ * it does not decide what a valid type name looks like.
36
+ */
37
+ export declare function parseContentTypeNames(raw: string): string[];
38
+ /** Appends `names` to `selected`, keeping order and dropping duplicates. */
39
+ export declare function addContentTypeNames(selected: readonly string[], names: readonly string[]): string[];
40
+ /** The endpoint's workspace filter, as the form holds it. */
41
+ export type WorkspaceSelection = {
42
+ /** Every workspace, including ones created later. */
43
+ allWorkspaces: boolean;
44
+ /** The chosen workspace ids, when `allWorkspaces` is off. */
45
+ workspaceIds: readonly string[];
46
+ };
47
+ /**
48
+ * The types worth offering for a given workspace filter.
49
+ *
50
+ * A delivery needs both halves of the filter to match — the event's workspace
51
+ * **and** its content type — so a type none of the chosen workspaces was granted
52
+ * can never fire for this endpoint. Offering it is offering a filter that
53
+ * guarantees silence, which is the mistake the editor exists to prevent.
54
+ *
55
+ * Two cases deliberately fall back to the whole registry rather than to
56
+ * nothing:
57
+ *
58
+ * - **`allWorkspaces`** — the endpoint covers workspaces that do not exist yet,
59
+ * and those may be granted anything.
60
+ * - **no workspace chosen at all** — there is nothing to narrow by. The caller
61
+ * decides whether to show the picker at all in that state; narrowing to an
62
+ * empty list here would be a guess dressed up as a rule.
63
+ */
64
+ export declare function grantedContentTypes(catalogue: readonly ContentTypeOption[], workspaces: readonly WorkspaceOption[], { allWorkspaces, workspaceIds }: WorkspaceSelection): ContentTypeOption[];
65
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/domain/contentTypeChoices/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC5B,iBAAiB,EAAE,CAYrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,EAAE,CAMV;AAED,6DAA6D;AAC7D,MAAM,MAAM,kBAAkB,GAAG;IAC7B,qDAAqD;IACrD,aAAa,EAAE,OAAO,CAAC;IACvB,6DAA6D;IAC7D,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,EAAE,aAAa,EAAE,YAAY,EAAE,EAAE,kBAAkB,GACpD,iBAAiB,EAAE,CASrB"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The picker's options: the registry's types, plus any already-selected name it
3
+ * does not know, appended in selection order.
4
+ *
5
+ * Dropping the unknown ones would be the worst possible behaviour here — the
6
+ * picker would render a subscription it cannot express, and the next save would
7
+ * silently widen the endpoint to types it was deliberately narrowed away from.
8
+ */
9
+ export function contentTypeChoices(catalogue, selected) {
10
+ const known = new Set(catalogue.map((type) => type.name));
11
+ return [
12
+ ...catalogue.map((type) => ({
13
+ value: type.name,
14
+ label: type.label,
15
+ known: true
16
+ })),
17
+ ...selected
18
+ .filter((name) => !known.has(name))
19
+ .map((name) => ({ value: name, label: name, known: false }))
20
+ ];
21
+ }
22
+ /**
23
+ * Reads typed-in machine names.
24
+ *
25
+ * Commas, whitespace and newlines all separate, because this field is pasted
26
+ * into as often as it is typed into, and `article, product` is what a paste
27
+ * looks like. The server accepts any string here — it must, or a type that does
28
+ * not exist yet could never be subscribed to — so this only trims and splits;
29
+ * it does not decide what a valid type name looks like.
30
+ */
31
+ export function parseContentTypeNames(raw) {
32
+ return raw
33
+ .split(/[\s,]+/)
34
+ .map((name) => name.trim())
35
+ .filter(Boolean);
36
+ }
37
+ /** Appends `names` to `selected`, keeping order and dropping duplicates. */
38
+ export function addContentTypeNames(selected, names) {
39
+ const next = [...selected];
40
+ for (const name of names) {
41
+ if (!next.includes(name))
42
+ next.push(name);
43
+ }
44
+ return next;
45
+ }
46
+ /**
47
+ * The types worth offering for a given workspace filter.
48
+ *
49
+ * A delivery needs both halves of the filter to match — the event's workspace
50
+ * **and** its content type — so a type none of the chosen workspaces was granted
51
+ * can never fire for this endpoint. Offering it is offering a filter that
52
+ * guarantees silence, which is the mistake the editor exists to prevent.
53
+ *
54
+ * Two cases deliberately fall back to the whole registry rather than to
55
+ * nothing:
56
+ *
57
+ * - **`allWorkspaces`** — the endpoint covers workspaces that do not exist yet,
58
+ * and those may be granted anything.
59
+ * - **no workspace chosen at all** — there is nothing to narrow by. The caller
60
+ * decides whether to show the picker at all in that state; narrowing to an
61
+ * empty list here would be a guess dressed up as a rule.
62
+ */
63
+ export function grantedContentTypes(catalogue, workspaces, { allWorkspaces, workspaceIds }) {
64
+ if (allWorkspaces || workspaceIds.length === 0)
65
+ return [...catalogue];
66
+ const granted = new Set(workspaces
67
+ .filter((workspace) => workspaceIds.includes(workspace.id))
68
+ .flatMap((workspace) => workspace.contentTypes));
69
+ return catalogue.filter((type) => granted.has(type.name));
70
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Which custom headers an endpoint may set.
3
+ *
4
+ * A **deliberate copy** of `@orthacms/webhooks-domain`'s `isAllowedCustomHeader`
5
+ * rather than an import: that package's barrel reaches `node:crypto` through
6
+ * the signature helpers, and pulling it into the browser bundle takes the whole
7
+ * admin down at load. The server enforces the same rule on every write — this
8
+ * copy only turns a `422` into an error beside the field that caused it.
9
+ */
10
+ /** Why a header name was refused, or `null` when it is fine. */
11
+ export type HeaderRejection = 'malformed' | 'reserved';
12
+ /**
13
+ * Checks one header name.
14
+ *
15
+ * Overwriting `X-Ortha-Signature` or `X-Ortha-Event` would let a delivery claim
16
+ * to be something it is not, and `Host` is how a request aimed at one virtual
17
+ * host gets served by another — so both are refused rather than silently
18
+ * dropped at send time.
19
+ */
20
+ export declare function rejectionFor(name: string): HeaderRejection | null;
21
+ /** One row of the headers editor. A row keeps its identity while it is edited. */
22
+ export type HeaderRow = {
23
+ key: string;
24
+ name: string;
25
+ value: string;
26
+ };
27
+ /** Turns the stored map into rows, in a stable order. */
28
+ export declare function toHeaderRows(headers: Record<string, string>): HeaderRow[];
29
+ /**
30
+ * Turns rows back into the map the API takes.
31
+ *
32
+ * Blank rows are dropped — an editor that has one empty row open is not
33
+ * expressing "send an empty header" — and a later row of the same name wins,
34
+ * because that is what the map would have done anyway.
35
+ */
36
+ export declare function fromHeaderRows(rows: readonly HeaderRow[]): Record<string, string>;
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/domain/headerRules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkBH,gEAAgE;AAChE,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,UAAU,CAAC;AAEvD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQjE;AAED,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAAE,CAMzE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC1B,IAAI,EAAE,SAAS,SAAS,EAAE,GAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}