@protobase/schema 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/webhooks/webhook-resources.d.ts +113 -0
- package/dist/webhooks/webhook-resources.js +211 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { InferRecord, RecordKey } from "./resource-types.js";
|
|
|
9
9
|
import { resource } from "./resource.js";
|
|
10
10
|
import { checkEventFilter } from "./events/event-models.js";
|
|
11
11
|
import { view } from "./view.js";
|
|
12
|
+
import { webhookResources } from "./webhooks/webhook-resources.js";
|
|
12
13
|
import { enumLabel } from "./enum-label.js";
|
|
13
14
|
import { SearchTerm, matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
|
|
14
15
|
import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
|
|
@@ -32,4 +33,4 @@ import "./access/index.js";
|
|
|
32
33
|
import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
|
|
33
34
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
34
35
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
35
|
-
export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type Capability, type CapabilityAction, type CapabilityScope, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type DatePreset, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldModel, type FieldPath, type FieldRule, type FieldType, type FieldViewModel, type FileField, type FileFieldModel, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type InferRecord, type KeyType, type KeyValue, type LayoutItemModel, type ListModel, type ListQuery, type Literal, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RelatedModel, type ResolveOptions, type ResolvedAccess, type ResourceModel, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchResultModel, type SearchTerm, type SortSpec, type Span, type StatusTone, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type ViewModel, accessWarnings, applyAssistantEvent, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldsUsedBy, keyTypes, layout as l, matchedSearchField, matchesSearch, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, printFilter, printOrderBy, readEventStream, resolveAccess, resource, restrictModel, rule, searchTerms, statusTones, termMatches, userMenu, view, where };
|
|
36
|
+
export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type Capability, type CapabilityAction, type CapabilityScope, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type DatePreset, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldModel, type FieldPath, type FieldRule, type FieldType, type FieldViewModel, type FileField, type FileFieldModel, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type InferRecord, type KeyType, type KeyValue, type LayoutItemModel, type ListModel, type ListQuery, type Literal, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RelatedModel, type ResolveOptions, type ResolvedAccess, type ResourceModel, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchResultModel, type SearchTerm, type SortSpec, type Span, type StatusTone, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type ViewModel, accessWarnings, applyAssistantEvent, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldsUsedBy, keyTypes, layout as l, matchedSearchField, matchesSearch, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, printFilter, printOrderBy, readEventStream, resolveAccess, resource, restrictModel, rule, searchTerms, statusTones, termMatches, userMenu, view, webhookResources, where };
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,13 @@ import { createWhere, where } from "./filter/where.js";
|
|
|
9
9
|
import { accessWarnings, checkRecord, fieldsUsedBy, resolveAccess } from "./access/resolve.js";
|
|
10
10
|
import { checkEventFilter } from "./events/event-models.js";
|
|
11
11
|
import { resource } from "./resource.js";
|
|
12
|
+
import { layout } from "./layout.js";
|
|
12
13
|
import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
|
|
13
14
|
import { view } from "./view.js";
|
|
15
|
+
import { webhookResources } from "./webhooks/webhook-resources.js";
|
|
14
16
|
import { enumLabel } from "./enum-label.js";
|
|
15
17
|
import { matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
|
|
16
18
|
import { userMenu } from "./user-menu.js";
|
|
17
|
-
import { layout } from "./layout.js";
|
|
18
19
|
import { decodeKey, encodeKey, keyTypes } from "./keys.js";
|
|
19
20
|
import { rule } from "./access/rule.js";
|
|
20
21
|
import { defineRoles } from "./access/roles.js";
|
|
@@ -22,4 +23,4 @@ import { pickView } from "./access/pick-view.js";
|
|
|
22
23
|
import { restrictModel } from "./access/restrict.js";
|
|
23
24
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
24
25
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
25
|
-
export { accessWarnings, applyAssistantEvent, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldsUsedBy, keyTypes, layout as l, matchedSearchField, matchesSearch, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, printFilter, printOrderBy, readEventStream, resolveAccess, resource, restrictModel, rule, searchTerms, statusTones, termMatches, userMenu, view, where };
|
|
26
|
+
export { accessWarnings, applyAssistantEvent, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldsUsedBy, keyTypes, layout as l, matchedSearchField, matchesSearch, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, printFilter, printOrderBy, readEventStream, resolveAccess, resource, restrictModel, rule, searchTerms, statusTones, termMatches, userMenu, view, webhookResources, where };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Field } from "../field.js";
|
|
2
|
+
import { Ref } from "../refs.js";
|
|
3
|
+
import { ResourceBuilder } from "../resource.js";
|
|
4
|
+
import { ViewBuilder } from "../view.js";
|
|
5
|
+
import "../index.js";
|
|
6
|
+
//#region src/webhooks/webhook-resources.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* The resources and views `webhooks()` adds to an app, over the tables and the view of `schema`: Webhook subscriptions,
|
|
9
|
+
* Webhook deliveries and, with `deliveryLog`, Webhook delivery log. Config only; the server delivers.
|
|
10
|
+
*/
|
|
11
|
+
export declare const webhookResources: (schema: string, { deliveryLog }?: {
|
|
12
|
+
deliveryLog?: boolean;
|
|
13
|
+
}) => {
|
|
14
|
+
webhookDeliveryLog?: ResourceBuilder<{
|
|
15
|
+
readonly id: Field<string>;
|
|
16
|
+
readonly subscriptionId: Field<string | number>;
|
|
17
|
+
readonly tenantId: Field<string | null>;
|
|
18
|
+
readonly eventId: Field<string>;
|
|
19
|
+
readonly eventType: Field<string>;
|
|
20
|
+
readonly status: Field<"failed" | "delivered">;
|
|
21
|
+
readonly attempt: Field<number>;
|
|
22
|
+
readonly statusCode: Field<number | null>;
|
|
23
|
+
readonly response: Field<string | null>;
|
|
24
|
+
readonly durationMs: Field<number>;
|
|
25
|
+
readonly at: Field<string>;
|
|
26
|
+
}, Ref<"id", string>> | undefined;
|
|
27
|
+
webhookDeliveryLogView?: ViewBuilder<ResourceBuilder<{
|
|
28
|
+
readonly id: Field<string>;
|
|
29
|
+
readonly subscriptionId: Field<string | number>;
|
|
30
|
+
readonly tenantId: Field<string | null>;
|
|
31
|
+
readonly eventId: Field<string>;
|
|
32
|
+
readonly eventType: Field<string>;
|
|
33
|
+
readonly status: Field<"failed" | "delivered">;
|
|
34
|
+
readonly attempt: Field<number>;
|
|
35
|
+
readonly statusCode: Field<number | null>;
|
|
36
|
+
readonly response: Field<string | null>;
|
|
37
|
+
readonly durationMs: Field<number>;
|
|
38
|
+
readonly at: Field<string>;
|
|
39
|
+
}, Ref<"id", string>>> | undefined;
|
|
40
|
+
webhookSubscriptions: ResourceBuilder<{
|
|
41
|
+
readonly id: Field<string>;
|
|
42
|
+
readonly name: Field<string>;
|
|
43
|
+
readonly events: Field<string>;
|
|
44
|
+
readonly url: Field<string>;
|
|
45
|
+
readonly filter: Field<string | null>;
|
|
46
|
+
readonly headers: Field<import("zod").JSONType>;
|
|
47
|
+
readonly payload: Field<"full" | "thin">;
|
|
48
|
+
readonly enabled: Field<boolean>;
|
|
49
|
+
readonly secret: Field<string>;
|
|
50
|
+
readonly previousSecret: null;
|
|
51
|
+
readonly previousSecretUntil: null;
|
|
52
|
+
readonly tenantId: Field<string | null>;
|
|
53
|
+
readonly ownerId: Field<string | null>;
|
|
54
|
+
readonly disabledReason: Field<string | null>;
|
|
55
|
+
readonly lastSuccessAt: Field<string | null>;
|
|
56
|
+
readonly failingSince: Field<string | null>;
|
|
57
|
+
readonly createdAt: Field<string>;
|
|
58
|
+
readonly updatedAt: Field<string>;
|
|
59
|
+
readonly rotateSecret: Field<boolean>;
|
|
60
|
+
readonly sendTest: Field<boolean>;
|
|
61
|
+
readonly resendFailed: Field<boolean>;
|
|
62
|
+
}, Ref<"id", string>>;
|
|
63
|
+
webhookSubscriptionsView: ViewBuilder<ResourceBuilder<{
|
|
64
|
+
readonly id: Field<string>;
|
|
65
|
+
readonly name: Field<string>;
|
|
66
|
+
readonly events: Field<string>;
|
|
67
|
+
readonly url: Field<string>;
|
|
68
|
+
readonly filter: Field<string | null>;
|
|
69
|
+
readonly headers: Field<import("zod").JSONType>;
|
|
70
|
+
readonly payload: Field<"full" | "thin">;
|
|
71
|
+
readonly enabled: Field<boolean>;
|
|
72
|
+
readonly secret: Field<string>;
|
|
73
|
+
readonly previousSecret: null;
|
|
74
|
+
readonly previousSecretUntil: null;
|
|
75
|
+
readonly tenantId: Field<string | null>;
|
|
76
|
+
readonly ownerId: Field<string | null>;
|
|
77
|
+
readonly disabledReason: Field<string | null>;
|
|
78
|
+
readonly lastSuccessAt: Field<string | null>;
|
|
79
|
+
readonly failingSince: Field<string | null>;
|
|
80
|
+
readonly createdAt: Field<string>;
|
|
81
|
+
readonly updatedAt: Field<string>;
|
|
82
|
+
readonly rotateSecret: Field<boolean>;
|
|
83
|
+
readonly sendTest: Field<boolean>;
|
|
84
|
+
readonly resendFailed: Field<boolean>;
|
|
85
|
+
}, Ref<"id", string>>>;
|
|
86
|
+
webhookDeliveries: ResourceBuilder<{
|
|
87
|
+
readonly id: Field<string>;
|
|
88
|
+
readonly subscriptionId: Field<string | number>;
|
|
89
|
+
readonly tenantId: Field<string | null>;
|
|
90
|
+
readonly eventType: Field<string>;
|
|
91
|
+
readonly eventId: Field<string>;
|
|
92
|
+
readonly status: Field<"waiting" | "failed">;
|
|
93
|
+
readonly attempt: Field<number>;
|
|
94
|
+
readonly nextAttempt: Field<string | null>;
|
|
95
|
+
readonly lastError: Field<string | null>;
|
|
96
|
+
readonly createdAt: Field<string>;
|
|
97
|
+
readonly resend: Field<boolean>;
|
|
98
|
+
}, Ref<"id", string>>;
|
|
99
|
+
webhookDeliveriesView: ViewBuilder<ResourceBuilder<{
|
|
100
|
+
readonly id: Field<string>;
|
|
101
|
+
readonly subscriptionId: Field<string | number>;
|
|
102
|
+
readonly tenantId: Field<string | null>;
|
|
103
|
+
readonly eventType: Field<string>;
|
|
104
|
+
readonly eventId: Field<string>;
|
|
105
|
+
readonly status: Field<"waiting" | "failed">;
|
|
106
|
+
readonly attempt: Field<number>;
|
|
107
|
+
readonly nextAttempt: Field<string | null>;
|
|
108
|
+
readonly lastError: Field<string | null>;
|
|
109
|
+
readonly createdAt: Field<string>;
|
|
110
|
+
readonly resend: Field<boolean>;
|
|
111
|
+
}, Ref<"id", string>>>;
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { f } from "../fields.js";
|
|
2
|
+
import { resource } from "../resource.js";
|
|
3
|
+
import { layout } from "../layout.js";
|
|
4
|
+
import { view } from "../view.js";
|
|
5
|
+
//#region src/webhooks/webhook-resources.ts
|
|
6
|
+
/** Webhook subscriptions: a row per subscription someone made in the admin app, scoped to their tenant. */
|
|
7
|
+
const subscriptionsResource = (schema) => resource("webhookSubscriptions").table(`${schema}.subscriptions`).fields({
|
|
8
|
+
id: f.uuid().readOnly().dbDefault(),
|
|
9
|
+
name: f.text().min(1).max(100).filterable().sortable(),
|
|
10
|
+
events: f.text().min(1).max(2e3),
|
|
11
|
+
url: f.text().max(2e3).regex(/^https?:\/\/[^\s/]+/, "Use an http:// or https:// URL"),
|
|
12
|
+
filter: f.text().optional().max(2e3),
|
|
13
|
+
headers: f.json().optional(),
|
|
14
|
+
payload: f.enum(["full", "thin"]).default("full"),
|
|
15
|
+
enabled: f.boolean().default(true).filterable(),
|
|
16
|
+
secret: f.text().sensitive().readOnly().dbDefault(),
|
|
17
|
+
previousSecret: null,
|
|
18
|
+
previousSecretUntil: null,
|
|
19
|
+
tenantId: f.text().optional().readOnly(),
|
|
20
|
+
ownerId: f.text().optional().readOnly().dbDefault(),
|
|
21
|
+
disabledReason: f.text().optional().readOnly(),
|
|
22
|
+
lastSuccessAt: f.timestamp().optional().readOnly().sortable(),
|
|
23
|
+
failingSince: f.timestamp().optional().readOnly(),
|
|
24
|
+
createdAt: f.timestamp().readOnly().dbDefault().sortable(),
|
|
25
|
+
updatedAt: f.timestamp().readOnly().dbDefault(),
|
|
26
|
+
rotateSecret: f.boolean().default(false),
|
|
27
|
+
sendTest: f.boolean().default(false),
|
|
28
|
+
resendFailed: f.boolean().default(false)
|
|
29
|
+
}).primaryKey((r) => r.id).tenant((r) => r.tenantId).owner((r) => r.ownerId).search((r) => [r.name, r.url]);
|
|
30
|
+
const subscriptionsView = view("webhookSubscriptions").names({
|
|
31
|
+
singular: "Webhook subscription",
|
|
32
|
+
plural: "Webhook subscriptions"
|
|
33
|
+
}).icon("webhook").title((r) => r.name).help("Sends the app's events to a URL as they happen, signed with Standard Webhooks. It delivers as its owner: only the records and fields they may read.").fields((r) => ({
|
|
34
|
+
events: r.events.help("Event names separated by spaces, such as orders.created, or orders.* for every event of a resource"),
|
|
35
|
+
url: r.url.label("URL"),
|
|
36
|
+
filter: r.filter.help("Only records that match, in the language of filters: status = \"paid\""),
|
|
37
|
+
headers: r.headers.help("Extra request headers, as a JSON object"),
|
|
38
|
+
payload: r.payload.help("Thin leaves the records out: the receiver reads them through the API").valueLabels({
|
|
39
|
+
full: "Full",
|
|
40
|
+
thin: "Thin"
|
|
41
|
+
}),
|
|
42
|
+
secret: r.secret.help("Receivers check the webhook-signature header with it"),
|
|
43
|
+
disabledReason: r.disabledReason.label("Disabled because"),
|
|
44
|
+
lastSuccessAt: r.lastSuccessAt.label("Last success"),
|
|
45
|
+
failingSince: r.failingSince.label("Failing since")
|
|
46
|
+
})).list((r) => ({
|
|
47
|
+
columns: [
|
|
48
|
+
r.name,
|
|
49
|
+
r.events,
|
|
50
|
+
r.url,
|
|
51
|
+
r.enabled,
|
|
52
|
+
r.lastSuccessAt
|
|
53
|
+
],
|
|
54
|
+
sort: [[r.createdAt, "desc"]]
|
|
55
|
+
})).layout((r) => [
|
|
56
|
+
layout.section("Subscription", [
|
|
57
|
+
r.name,
|
|
58
|
+
r.events,
|
|
59
|
+
r.url,
|
|
60
|
+
r.filter,
|
|
61
|
+
r.payload,
|
|
62
|
+
r.headers,
|
|
63
|
+
r.enabled
|
|
64
|
+
]),
|
|
65
|
+
layout.section("Signing", [r.secret]),
|
|
66
|
+
layout.sidebar([
|
|
67
|
+
r.lastSuccessAt,
|
|
68
|
+
r.failingSince,
|
|
69
|
+
r.disabledReason,
|
|
70
|
+
r.createdAt
|
|
71
|
+
]),
|
|
72
|
+
layout.related("Waiting and failed deliveries", {
|
|
73
|
+
resource: "webhookDeliveries",
|
|
74
|
+
field: "subscriptionId",
|
|
75
|
+
columns: [
|
|
76
|
+
"eventType",
|
|
77
|
+
"status",
|
|
78
|
+
"attempt",
|
|
79
|
+
"nextAttempt",
|
|
80
|
+
"lastError"
|
|
81
|
+
]
|
|
82
|
+
})
|
|
83
|
+
]).actions((a) => [
|
|
84
|
+
a.update("sendTest", {
|
|
85
|
+
label: "Send test event",
|
|
86
|
+
icon: "send",
|
|
87
|
+
set: { sendTest: true }
|
|
88
|
+
}),
|
|
89
|
+
a.update("resendFailed", {
|
|
90
|
+
label: "Resend failed",
|
|
91
|
+
icon: "rotate-ccw",
|
|
92
|
+
set: { resendFailed: true },
|
|
93
|
+
confirm: "Try every failed delivery of this subscription once more?"
|
|
94
|
+
}),
|
|
95
|
+
a.update("rotateSecret", {
|
|
96
|
+
label: "Rotate secret",
|
|
97
|
+
icon: "key-round",
|
|
98
|
+
set: { rotateSecret: true },
|
|
99
|
+
confirm: "Make a new secret? The old one keeps signing too for 24 hours."
|
|
100
|
+
}),
|
|
101
|
+
a.update("disable", {
|
|
102
|
+
label: "Disable",
|
|
103
|
+
icon: "pause",
|
|
104
|
+
set: { enabled: false }
|
|
105
|
+
})
|
|
106
|
+
]);
|
|
107
|
+
/** The deliveries of webhook subscriptions that wait for a retry or failed for good: delivery jobs, not a table. */
|
|
108
|
+
const deliveriesResource = (schema) => resource("webhookDeliveries").table(`${schema}.deliveries`).fields({
|
|
109
|
+
id: f.uuid().readOnly(),
|
|
110
|
+
subscriptionId: f.relation("webhookSubscriptions").readOnly().filterable(),
|
|
111
|
+
tenantId: f.text().optional().readOnly(),
|
|
112
|
+
eventType: f.text().readOnly().filterable().sortable(),
|
|
113
|
+
eventId: f.text().readOnly().filterable(),
|
|
114
|
+
status: f.enum(["waiting", "failed"]).readOnly().filterable(),
|
|
115
|
+
attempt: f.integer().readOnly(),
|
|
116
|
+
nextAttempt: f.timestamp().optional().readOnly().sortable(),
|
|
117
|
+
lastError: f.text().optional().readOnly(),
|
|
118
|
+
createdAt: f.timestamp().readOnly().sortable(),
|
|
119
|
+
resend: f.boolean()
|
|
120
|
+
}).primaryKey((r) => r.id).tenant((r) => r.tenantId).access({
|
|
121
|
+
create: () => false,
|
|
122
|
+
delete: () => false
|
|
123
|
+
});
|
|
124
|
+
const deliveriesView = view("webhookDeliveries").names({
|
|
125
|
+
singular: "Webhook delivery",
|
|
126
|
+
plural: "Webhook deliveries"
|
|
127
|
+
}).icon("send").title((r) => r.eventType).help("Deliveries that wait for their next try or failed for good. A delivery leaves the list once it succeeds.").fields((r) => ({
|
|
128
|
+
eventType: r.eventType.label("Event"),
|
|
129
|
+
eventId: r.eventId.label("Event id").format("code"),
|
|
130
|
+
nextAttempt: r.nextAttempt.label("Next try"),
|
|
131
|
+
lastError: r.lastError.label("Last error"),
|
|
132
|
+
attempt: r.attempt.label("Tries")
|
|
133
|
+
})).list((r) => ({
|
|
134
|
+
columns: [
|
|
135
|
+
r.eventType,
|
|
136
|
+
r.subscriptionId,
|
|
137
|
+
r.status,
|
|
138
|
+
r.attempt,
|
|
139
|
+
r.nextAttempt,
|
|
140
|
+
r.lastError
|
|
141
|
+
],
|
|
142
|
+
sort: [[r.createdAt, "desc"]]
|
|
143
|
+
})).layout((r) => [layout.section("Delivery", [
|
|
144
|
+
r.subscriptionId,
|
|
145
|
+
r.eventType,
|
|
146
|
+
r.eventId,
|
|
147
|
+
r.lastError
|
|
148
|
+
]), layout.sidebar([
|
|
149
|
+
r.status,
|
|
150
|
+
r.attempt,
|
|
151
|
+
r.nextAttempt,
|
|
152
|
+
r.createdAt
|
|
153
|
+
])]).actions((a) => [a.update("resend", {
|
|
154
|
+
label: "Resend",
|
|
155
|
+
icon: "rotate-ccw",
|
|
156
|
+
set: { resend: true },
|
|
157
|
+
confirm: "Try this delivery once more, from now?"
|
|
158
|
+
})]);
|
|
159
|
+
/** Every try of every webhook subscription's deliveries, for 30 days (`deliveryLog: true`). */
|
|
160
|
+
const deliveryLogResource = (schema) => resource("webhookDeliveryLog").table(`${schema}.delivery_log`).fields({
|
|
161
|
+
id: f.bigint().readOnly(),
|
|
162
|
+
subscriptionId: f.relation("webhookSubscriptions").readOnly().filterable(),
|
|
163
|
+
tenantId: f.text().optional().readOnly(),
|
|
164
|
+
eventId: f.text().readOnly().filterable(),
|
|
165
|
+
eventType: f.text().readOnly().filterable().sortable(),
|
|
166
|
+
status: f.enum(["delivered", "failed"]).readOnly().filterable(),
|
|
167
|
+
attempt: f.integer().readOnly(),
|
|
168
|
+
statusCode: f.integer().optional().readOnly().filterable(),
|
|
169
|
+
response: f.text().optional().readOnly(),
|
|
170
|
+
durationMs: f.integer().readOnly(),
|
|
171
|
+
at: f.timestamp().readOnly().filterable().sortable()
|
|
172
|
+
}).primaryKey((r) => r.id).tenant((r) => r.tenantId).access({
|
|
173
|
+
create: () => false,
|
|
174
|
+
update: () => false,
|
|
175
|
+
delete: () => false
|
|
176
|
+
});
|
|
177
|
+
const deliveryLogView = view("webhookDeliveryLog").names({
|
|
178
|
+
singular: "Webhook delivery log entry",
|
|
179
|
+
plural: "Webhook delivery log"
|
|
180
|
+
}).icon("scroll-text").title((r) => r.eventType).fields((r) => ({
|
|
181
|
+
eventType: r.eventType.label("Event"),
|
|
182
|
+
statusCode: r.statusCode.label("Status code"),
|
|
183
|
+
durationMs: r.durationMs.label("Duration (ms)"),
|
|
184
|
+
at: r.at.label("When")
|
|
185
|
+
})).list((r) => ({
|
|
186
|
+
columns: [
|
|
187
|
+
r.at,
|
|
188
|
+
r.eventType,
|
|
189
|
+
r.subscriptionId,
|
|
190
|
+
r.status,
|
|
191
|
+
r.statusCode,
|
|
192
|
+
r.durationMs
|
|
193
|
+
],
|
|
194
|
+
sort: [[r.at, "desc"]]
|
|
195
|
+
}));
|
|
196
|
+
/**
|
|
197
|
+
* The resources and views `webhooks()` adds to an app, over the tables and the view of `schema`: Webhook subscriptions,
|
|
198
|
+
* Webhook deliveries and, with `deliveryLog`, Webhook delivery log. Config only; the server delivers.
|
|
199
|
+
*/
|
|
200
|
+
const webhookResources = (schema, { deliveryLog = false } = {}) => ({
|
|
201
|
+
webhookSubscriptions: subscriptionsResource(schema),
|
|
202
|
+
webhookSubscriptionsView: subscriptionsView,
|
|
203
|
+
webhookDeliveries: deliveriesResource(schema),
|
|
204
|
+
webhookDeliveriesView: deliveriesView,
|
|
205
|
+
...deliveryLog && {
|
|
206
|
+
webhookDeliveryLog: deliveryLogResource(schema),
|
|
207
|
+
webhookDeliveryLogView: deliveryLogView
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
//#endregion
|
|
211
|
+
export { webhookResources };
|