@isomorph.ai/app-sdk 1.12.0 → 1.13.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 CHANGED
@@ -238,7 +238,24 @@ export interface GmailMessageSendRequest {
238
238
  /** A fresh UUID per authorized Send action, reused only to retry that action. */
239
239
  idempotencyKey: string;
240
240
  }
241
- export type IntegrationRequest = SlackChannelHistoryRequest | SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest | SlackDmSendRequest | SlackUserLookupRequest | WarehouseViewReadRequest | GmailThreadListRequest | GmailThreadReadRequest | GmailMessageReadRequest | GmailAttachmentReadRequest | GmailMessageSendRequest;
241
+ export interface ServiceRequest {
242
+ operation: 'service.request';
243
+ resource?: never;
244
+ input: {
245
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
246
+ path: string;
247
+ pathParams?: Record<string, string | number>;
248
+ query?: Record<string, string | number | boolean>;
249
+ headers?: Record<string, string>;
250
+ body?: Record<string, unknown>;
251
+ };
252
+ }
253
+ export interface ServiceRequestResult {
254
+ status: number;
255
+ headers: Record<string, string>;
256
+ body: unknown;
257
+ }
258
+ export type IntegrationRequest = ServiceRequest | SlackChannelHistoryRequest | SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest | SlackDmSendRequest | SlackUserLookupRequest | WarehouseViewReadRequest | GmailThreadListRequest | GmailThreadReadRequest | GmailMessageReadRequest | GmailAttachmentReadRequest | GmailMessageSendRequest;
242
259
  export type IntegrationOperation = IntegrationRequest['operation'];
243
260
  export interface SlackMessage {
244
261
  id: string;
@@ -324,7 +341,7 @@ export interface GmailMessageSendResult {
324
341
  messageId: string;
325
342
  threadId: string;
326
343
  }
327
- export type IntegrationResult<R extends IntegrationRequest> = R extends SlackChannelHistoryRequest ? SlackChannelHistoryResult : R extends SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest ? SlackMessagePostResult : R extends SlackDmSendRequest ? SlackDmSendResult : R extends SlackUserLookupRequest ? SlackUserLookupResult : R extends WarehouseViewReadRequest ? WarehouseViewReadResult : R extends GmailThreadListRequest ? GmailThreadListResult : R extends GmailThreadReadRequest ? GmailThreadReadResult : R extends GmailMessageReadRequest ? GmailMessageReadResult : R extends GmailAttachmentReadRequest ? GmailAttachmentReadResult : R extends GmailMessageSendRequest ? GmailMessageSendResult : never;
344
+ export type IntegrationResult<R extends IntegrationRequest> = R extends ServiceRequest ? ServiceRequestResult : R extends SlackChannelHistoryRequest ? SlackChannelHistoryResult : R extends SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest ? SlackMessagePostResult : R extends SlackDmSendRequest ? SlackDmSendResult : R extends SlackUserLookupRequest ? SlackUserLookupResult : R extends WarehouseViewReadRequest ? WarehouseViewReadResult : R extends GmailThreadListRequest ? GmailThreadListResult : R extends GmailThreadReadRequest ? GmailThreadReadResult : R extends GmailMessageReadRequest ? GmailMessageReadResult : R extends GmailAttachmentReadRequest ? GmailAttachmentReadResult : R extends GmailMessageSendRequest ? GmailMessageSendResult : never;
328
345
  export type IntegrationConnectResult = {
329
346
  status: 'connected';
330
347
  accountLabel: string;
package/dist/index.js CHANGED
@@ -189,7 +189,13 @@ class Transport {
189
189
  const response = await this.fetch(`${this.baseUrl}${path}`, {
190
190
  credentials: 'same-origin',
191
191
  ...init,
192
- headers: mergeHeaders({ 'content-type': 'application/json' }, runnerIdentityHeaders(this.identity), init.headers, onProgress ? { accept: PROGRESS_MEDIA_TYPE } : undefined),
192
+ headers: mergeHeaders(
193
+ // No content type on a request without a body: a browser GET that
194
+ // carries one is not a "simple" request, so it costs a CORS preflight
195
+ // (OPTIONS, one more round trip, on a separate uncredentialed
196
+ // connection a preconnect cannot warm) before the GET itself. Without
197
+ // it the identity GET and every other read is one round trip.
198
+ init.body === undefined && ['GET', 'HEAD'].includes((init.method ?? 'GET').toUpperCase()) ? {} : { 'content-type': 'application/json' }, runnerIdentityHeaders(this.identity), init.headers, onProgress ? { accept: PROGRESS_MEDIA_TYPE } : undefined),
193
199
  });
194
200
  if (onProgress && response.ok && response.body && response.headers.get('content-type')?.startsWith(PROGRESS_MEDIA_TYPE)) {
195
201
  return readProgress(response, onProgress);
@@ -737,7 +743,7 @@ export function createClient(options = {}) {
737
743
  // operations accept `mode`; identity and stray fields are not sent.
738
744
  const { operation, resource, input, idempotencyKey, mode } = request;
739
745
  return transport.json('/_harbour/integrations/execute', {
740
- method: 'POST', body: JSON.stringify({ connection, operation, resource, ...(input !== undefined ? { input } : {}), ...(idempotencyKey !== undefined ? { idempotencyKey } : {}), ...(IntegrationOperations[operation]?.declaredMode && mode !== undefined ? { mode } : {}) }),
746
+ method: 'POST', body: JSON.stringify({ connection, operation, ...(operation !== 'service.request' ? { resource } : {}), ...(input !== undefined ? { input } : {}), ...(idempotencyKey !== undefined ? { idempotencyKey } : {}), ...(IntegrationOperations[operation]?.declaredMode && mode !== undefined ? { mode } : {}) }),
741
747
  });
742
748
  },
743
749
  // In a browser the consent returns the person to this page (without its
@@ -6,6 +6,9 @@
6
6
  * same message wherever it is made.
7
7
  */
8
8
  export type IntegrationExecuteInputFieldRule = {
9
+ kind: 'object';
10
+ required: boolean;
11
+ } | {
9
12
  kind: 'integer';
10
13
  required: boolean;
11
14
  minimum: number;
@@ -461,6 +464,38 @@ export declare const IntegrationExecuteInputRules: {
461
464
  };
462
465
  };
463
466
  };
467
+ readonly "service.request": {
468
+ readonly idempotencyKey: false;
469
+ readonly fields: {
470
+ readonly method: {
471
+ readonly kind: "enum";
472
+ readonly required: true;
473
+ readonly values: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
474
+ };
475
+ readonly path: {
476
+ readonly kind: "string";
477
+ readonly required: true;
478
+ readonly minLength: 0;
479
+ readonly maxLength: 16384;
480
+ };
481
+ readonly pathParams: {
482
+ readonly kind: "object";
483
+ readonly required: false;
484
+ };
485
+ readonly query: {
486
+ readonly kind: "object";
487
+ readonly required: false;
488
+ };
489
+ readonly headers: {
490
+ readonly kind: "object";
491
+ readonly required: false;
492
+ };
493
+ readonly body: {
494
+ readonly kind: "object";
495
+ readonly required: false;
496
+ };
497
+ };
498
+ };
464
499
  };
465
500
  export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
466
501
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
@@ -113,6 +113,17 @@ export const IntegrationExecuteInputRules = {
113
113
  limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
114
114
  },
115
115
  },
116
+ "service.request": {
117
+ idempotencyKey: false,
118
+ fields: {
119
+ method: { kind: "enum", required: true, values: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] },
120
+ path: { kind: "string", required: true, minLength: 0, maxLength: 16384 },
121
+ pathParams: { kind: "object", required: false },
122
+ query: { kind: "object", required: false },
123
+ headers: { kind: "object", required: false },
124
+ body: { kind: "object", required: false },
125
+ },
126
+ },
116
127
  };
117
128
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
118
129
  export function integrationExecuteInputMaximum(operation, field) {
@@ -173,6 +184,10 @@ function checkFields(operation, prefix, fields, given, nullIsAbsent) {
173
184
  }
174
185
  function checkField(operation, path, name, field, value) {
175
186
  switch (field.kind) {
187
+ case 'object':
188
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
189
+ return refuse(path, `${path} must be a JSON object`);
190
+ return { ok: true, value };
176
191
  case 'integer':
177
192
  if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum)
178
193
  return refuse(path, `${path} must be an integer between ${field.minimum} and ${field.maximum}`);
@@ -84,4 +84,11 @@ export declare const IntegrationOperations: {
84
84
  readonly declaredMode: false;
85
85
  readonly idempotencyKey: false;
86
86
  };
87
+ readonly "service.request": {
88
+ readonly kind: "service";
89
+ readonly identities: readonly ["app"];
90
+ readonly readOnly: false;
91
+ readonly declaredMode: false;
92
+ readonly idempotencyKey: false;
93
+ };
87
94
  };
@@ -113,5 +113,14 @@ export const IntegrationOperations = {
113
113
  "readOnly": true,
114
114
  "declaredMode": false,
115
115
  "idempotencyKey": false
116
+ },
117
+ "service.request": {
118
+ "kind": "service",
119
+ "identities": [
120
+ "app"
121
+ ],
122
+ "readOnly": false,
123
+ "declaredMode": false,
124
+ "idempotencyKey": false
116
125
  }
117
126
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",