deepline 0.2.38 → 0.2.40

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.
@@ -36,6 +36,7 @@
36
36
  import { resolveConfig } from './config.js';
37
37
  import { DeeplineError } from './errors.js';
38
38
  import { HttpClient } from './http.js';
39
+ import { PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES } from '../../shared_libs/product-notifications/contract.js';
39
40
  import {
40
41
  STREAM_HEALTHY_CONNECTION_MS,
41
42
  isTransientPlayStreamError,
@@ -58,6 +59,7 @@ import type {
58
59
  PlayRunPackage,
59
60
  PlayLiveEvent,
60
61
  PlayListItem,
62
+ ProductNotificationSettings,
61
63
  PlayDescription,
62
64
  StopPlayRunResult,
63
65
  StopAllPlayRunsResult,
@@ -3742,6 +3744,145 @@ export class DeeplineClient {
3742
3744
  return response.plays ?? [];
3743
3745
  }
3744
3746
 
3747
+ /** Read product-notification destinations, subscriptions, event catalog, and DLQ health. */
3748
+ async getNotificationSettings(): Promise<ProductNotificationSettings> {
3749
+ return this.http.get('/api/v2/settings/notifications');
3750
+ }
3751
+
3752
+ /** Start the Slack OAuth flow required by product notifications. */
3753
+ async connectNotificationSlack(options?: {
3754
+ successUrl?: string;
3755
+ failureUrl?: string;
3756
+ }): Promise<{ ok: boolean; redirect_url: string }> {
3757
+ return this.http.post('/api/v2/integrations/connect', {
3758
+ provider: 'slack',
3759
+ scopes: [...PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES],
3760
+ ...(options?.successUrl ? { success_url: options.successUrl } : {}),
3761
+ ...(options?.failureUrl ? { failure_url: options.failureUrl } : {}),
3762
+ });
3763
+ }
3764
+
3765
+ /** List Slack channels visible to the connected Deepline Slack app. */
3766
+ async listNotificationSlackChannels(query?: string): Promise<{
3767
+ identity: { teamId: string; teamName?: string };
3768
+ channels: Array<{ id: string; name: string; isPrivate: boolean }>;
3769
+ }> {
3770
+ const suffix = query ? `?query=${encodeURIComponent(query)}` : '';
3771
+ return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
3772
+ }
3773
+
3774
+ /** Select the Slack channel used for product notifications. */
3775
+ async setNotificationSlack(channel: string) {
3776
+ return this.http.put('/api/v2/settings/notifications', { channel });
3777
+ }
3778
+
3779
+ /** Send one synchronous test ping and return Slack's delivery result. */
3780
+ async testNotificationSlack(): Promise<{
3781
+ ok: boolean;
3782
+ deliveryId: string;
3783
+ state: string;
3784
+ message: string;
3785
+ }> {
3786
+ return this.http.post('/api/v2/settings/notifications/test', {});
3787
+ }
3788
+
3789
+ /** Disable Slack product notifications without deleting the OAuth connection. */
3790
+ async disableNotificationSlack() {
3791
+ return this.http.delete('/api/v2/settings/notifications');
3792
+ }
3793
+
3794
+ /** Enable or disable event IDs from the server-provided notification catalog. */
3795
+ async setNotificationSubscriptions(eventTypes: string[], enabled: boolean) {
3796
+ return this.http.patch('/api/v2/settings/notifications/subscriptions', {
3797
+ eventTypes,
3798
+ enabled,
3799
+ });
3800
+ }
3801
+
3802
+ /** List exhausted deliveries. Dead-lettered messages never replay automatically. */
3803
+ async listNotificationDlq(limit = 25) {
3804
+ return this.http.get(
3805
+ `/api/v2/settings/notifications/dlq?limit=${encodeURIComponent(String(limit))}`,
3806
+ );
3807
+ }
3808
+
3809
+ /** Inspect one exhausted notification delivery. */
3810
+ async getNotificationDlqDelivery(deliveryId: string) {
3811
+ return this.http.get(
3812
+ `/api/v2/settings/notifications/dlq/${encodeURIComponent(deliveryId)}`,
3813
+ );
3814
+ }
3815
+
3816
+ /** Explicitly retry or archive one dead-lettered notification delivery. */
3817
+ async updateNotificationDlqDelivery(
3818
+ deliveryId: string,
3819
+ action: 'retry' | 'archive',
3820
+ ) {
3821
+ return this.http.post(
3822
+ `/api/v2/settings/notifications/dlq/${encodeURIComponent(deliveryId)}`,
3823
+ { action },
3824
+ );
3825
+ }
3826
+
3827
+ /** List the workspace's named notification rules. */
3828
+ async getNotifications(): Promise<ProductNotificationSettings> {
3829
+ return this.http.get('/api/v2/notifications');
3830
+ }
3831
+
3832
+ /** List Slack channels available to an already-connected Slack integration. */
3833
+ async listNotificationChannels(query?: string): Promise<{
3834
+ identity: { teamId: string; teamName?: string };
3835
+ channels: Array<{ id: string; name: string; isPrivate: boolean }>;
3836
+ }> {
3837
+ const suffix = query ? `?search=${encodeURIComponent(query)}` : '';
3838
+ return this.http.get(`/api/v2/notifications/slack/channels${suffix}`);
3839
+ }
3840
+
3841
+ /** Create a named notification routed through an existing provider integration. */
3842
+ async createNotification(input: {
3843
+ name: string;
3844
+ provider: 'slack';
3845
+ channel: string;
3846
+ eventTypes: string[];
3847
+ }) {
3848
+ return this.http.post('/api/v2/notifications', input);
3849
+ }
3850
+
3851
+ /** Update a notification's target, event selection, or enabled state. */
3852
+ async updateNotification(
3853
+ notificationId: string,
3854
+ input:
3855
+ | { enabled: boolean }
3856
+ | { name: string; channel: string; eventTypes: string[] },
3857
+ ) {
3858
+ return this.http.patch(
3859
+ `/api/v2/notifications/${encodeURIComponent(notificationId)}`,
3860
+ input,
3861
+ );
3862
+ }
3863
+
3864
+ /** Send a validation ping to one notification. */
3865
+ async testNotification(notificationId: string): Promise<{
3866
+ ok: boolean;
3867
+ deliveryId: string;
3868
+ state: string;
3869
+ message: string;
3870
+ }> {
3871
+ return this.http.post(
3872
+ `/api/v2/notifications/${encodeURIComponent(notificationId)}/test`,
3873
+ {},
3874
+ );
3875
+ }
3876
+
3877
+ /** Archive one notification without touching its provider integration. */
3878
+ async deleteNotification(
3879
+ notificationId: string,
3880
+ ): Promise<{ deleted: boolean; id: string }> {
3881
+ return this.http.delete(
3882
+ `/api/v2/notifications/${encodeURIComponent(notificationId)}`,
3883
+ );
3884
+ }
3885
+
3745
3886
  /**
3746
3887
  * Search callable plays and return compact play descriptions.
3747
3888
  *
@@ -720,6 +720,14 @@ export class HttpClient {
720
720
  });
721
721
  }
722
722
 
723
+ async put<T = unknown>(
724
+ path: string,
725
+ body?: unknown,
726
+ headers?: Record<string, string>,
727
+ ): Promise<T> {
728
+ return this.request<T>(path, { method: 'PUT', body, headers });
729
+ }
730
+
723
731
  /**
724
732
  * Send a DELETE request.
725
733
  *
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
160
160
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
161
161
  // exposed storage-dependent synchronous access. This deliberate minor
162
162
  // release keeps lazy paging semantics independent of row residency.
163
- version: '0.2.38',
163
+ version: '0.2.40',
164
164
  contracts: {
165
165
  api: {
166
166
  name: 'sdk-http-api',
@@ -1084,6 +1084,48 @@ export interface PlayListItem {
1084
1084
  currentRevision?: PlayRevisionSummary | null;
1085
1085
  liveRevision?: PlayRevisionSummary | null;
1086
1086
  aliases?: string[];
1087
+ triggerStatus?: {
1088
+ cron: string | null;
1089
+ webhook: string | null;
1090
+ blockedReason: string | null;
1091
+ };
1092
+ }
1093
+
1094
+ export interface ProductNotificationEventDefinition {
1095
+ id: string;
1096
+ label: string;
1097
+ description: string;
1098
+ source: 'cron' | 'webhook';
1099
+ outcome: 'succeeded' | 'failed';
1100
+ defaultEnabled: boolean;
1101
+ }
1102
+
1103
+ export interface ProductNotificationSettings {
1104
+ contractVersion: number;
1105
+ catalog: ProductNotificationEventDefinition[];
1106
+ notifications?: ProductNotification[];
1107
+ providers?: Array<{ kind: string; targetLabel: string }>;
1108
+ destinations: Array<Record<string, unknown>>;
1109
+ subscriptions: Array<{
1110
+ eventType: string;
1111
+ enabled: boolean;
1112
+ destinationId: string;
1113
+ }>;
1114
+ dlq: { count: number; capped: boolean };
1115
+ slackConnection?: { connected: boolean; status: string };
1116
+ }
1117
+
1118
+ export interface ProductNotification {
1119
+ id: string;
1120
+ name: string;
1121
+ provider: string;
1122
+ target: { id: string; name: string };
1123
+ enabled: boolean;
1124
+ status: string;
1125
+ eventTypes: string[];
1126
+ lastTestedAt?: number;
1127
+ lastTestStatus?: string;
1128
+ lastErrorMessage?: string;
1087
1129
  }
1088
1130
 
1089
1131
  export interface PlayDescription {
@@ -0,0 +1,134 @@
1
+ export const PRODUCT_NOTIFICATION_CONTRACT_VERSION = 1 as const;
2
+
3
+ export const PRODUCT_NOTIFICATION_EVENT_CATALOG = [
4
+ {
5
+ id: 'play.cron.succeeded',
6
+ label: 'Cron success',
7
+ description: 'A scheduled Play run completed successfully.',
8
+ source: 'cron',
9
+ outcome: 'succeeded',
10
+ defaultEnabled: true,
11
+ },
12
+ {
13
+ id: 'play.cron.failed',
14
+ label: 'Cron failure',
15
+ description:
16
+ 'A scheduled Play could not start or its run reached a failed terminal state.',
17
+ source: 'cron',
18
+ outcome: 'failed',
19
+ defaultEnabled: true,
20
+ },
21
+ {
22
+ id: 'play.webhook.succeeded',
23
+ label: 'Webhook success',
24
+ description: 'An accepted webhook-triggered Play completed successfully.',
25
+ source: 'webhook',
26
+ outcome: 'succeeded',
27
+ defaultEnabled: true,
28
+ },
29
+ {
30
+ id: 'play.webhook.failed',
31
+ label: 'Webhook failure',
32
+ description:
33
+ 'An accepted webhook-triggered Play reached a failed terminal state.',
34
+ source: 'webhook',
35
+ outcome: 'failed',
36
+ defaultEnabled: true,
37
+ },
38
+ ] as const;
39
+
40
+ export type ProductNotificationEventDefinition =
41
+ (typeof PRODUCT_NOTIFICATION_EVENT_CATALOG)[number];
42
+ export type ProductNotificationEventType =
43
+ ProductNotificationEventDefinition['id'];
44
+ export type ProductNotificationSource =
45
+ ProductNotificationEventDefinition['source'];
46
+ export type ProductNotificationOutcome =
47
+ ProductNotificationEventDefinition['outcome'];
48
+
49
+ const PRODUCT_NOTIFICATION_EVENT_TYPE_SET = new Set<string>(
50
+ PRODUCT_NOTIFICATION_EVENT_CATALOG.map((event) => event.id),
51
+ );
52
+
53
+ export function isProductNotificationEventType(
54
+ value: unknown,
55
+ ): value is ProductNotificationEventType {
56
+ return (
57
+ typeof value === 'string' && PRODUCT_NOTIFICATION_EVENT_TYPE_SET.has(value)
58
+ );
59
+ }
60
+
61
+ export function getProductNotificationEventDefinition(
62
+ eventType: string,
63
+ ): ProductNotificationEventDefinition | null {
64
+ return (
65
+ PRODUCT_NOTIFICATION_EVENT_CATALOG.find(
66
+ (definition) => definition.id === eventType,
67
+ ) ?? null
68
+ );
69
+ }
70
+
71
+ export function defaultProductNotificationEventTypes(): ProductNotificationEventType[] {
72
+ return PRODUCT_NOTIFICATION_EVENT_CATALOG.filter(
73
+ (definition) => definition.defaultEnabled,
74
+ ).map((definition) => definition.id);
75
+ }
76
+
77
+ export const PRODUCT_NOTIFICATION_DESTINATION_KINDS = ['slack'] as const;
78
+ export const PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
79
+ 'channels:read',
80
+ 'chat:write',
81
+ 'groups:read',
82
+ ] as const;
83
+ export type ProductNotificationDestinationKind =
84
+ (typeof PRODUCT_NOTIFICATION_DESTINATION_KINDS)[number];
85
+
86
+ export const PRODUCT_NOTIFICATION_DELIVERY_STATES = [
87
+ 'pending',
88
+ 'delivering',
89
+ 'delivered',
90
+ 'retry_scheduled',
91
+ 'dead_lettered',
92
+ 'expired',
93
+ 'suppressed',
94
+ ] as const;
95
+ export type ProductNotificationDeliveryState =
96
+ (typeof PRODUCT_NOTIFICATION_DELIVERY_STATES)[number];
97
+
98
+ /** Immediate send plus four bounded retries. */
99
+ export const PRODUCT_NOTIFICATION_RETRY_DELAYS_MS = [
100
+ 0,
101
+ 60_000,
102
+ 5 * 60_000,
103
+ 15 * 60_000,
104
+ 60 * 60_000,
105
+ ] as const;
106
+ export const PRODUCT_NOTIFICATION_MAX_ATTEMPTS =
107
+ PRODUCT_NOTIFICATION_RETRY_DELAYS_MS.length;
108
+ /** A claimed delivery must finish within this window or a recovery attempt may claim it. */
109
+ export const PRODUCT_NOTIFICATION_DELIVERY_LEASE_MS = 2 * 60_000;
110
+
111
+ export const PRODUCT_NOTIFICATION_PENDING_LIMIT_PER_DESTINATION = 100;
112
+ export const PRODUCT_NOTIFICATION_DLQ_REPLAY_LIMIT = 25;
113
+ export const PRODUCT_NOTIFICATION_SUCCESS_TTL_MS = 15 * 60_000;
114
+ export const PRODUCT_NOTIFICATION_FAILURE_TTL_MS = 24 * 60 * 60_000;
115
+
116
+ export function productNotificationEventTypeFor(input: {
117
+ source: ProductNotificationSource;
118
+ outcome: ProductNotificationOutcome;
119
+ }): ProductNotificationEventType {
120
+ return `play.${input.source}.${input.outcome}` as ProductNotificationEventType;
121
+ }
122
+
123
+ export function productNotificationEventExpiresAt(input: {
124
+ eventType: ProductNotificationEventType;
125
+ occurredAt: number;
126
+ }): number {
127
+ const definition = getProductNotificationEventDefinition(input.eventType);
128
+ return (
129
+ input.occurredAt +
130
+ (definition?.outcome === 'succeeded'
131
+ ? PRODUCT_NOTIFICATION_SUCCESS_TTL_MS
132
+ : PRODUCT_NOTIFICATION_FAILURE_TTL_MS)
133
+ );
134
+ }