@isomorph.ai/app-sdk 1.26.0 → 1.28.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/README.md CHANGED
@@ -482,3 +482,30 @@ On a serverless cell Harbour runs an app's server code as Lambda functions, from
482
482
  `ISOMORPH_JOBS_QUEUE_URL`: `isomorph.jobs.enqueue` writes the run, then sends one message to this SQS queue; the runtime never works the queue itself.
483
483
 
484
484
  (1.24.0; data plane ADR 0065)
485
+
486
+ ### Email-arrival jobs
487
+
488
+ On managed serverless hosting, an ordinary job can declare an inbox trigger instead of a schedule:
489
+
490
+ ```ts
491
+ export const trigger = {
492
+ type: 'email.arrived',
493
+ connection: 'company-gmail',
494
+ resource: 'inbox',
495
+ filter: { subjectContains: 'Invoice' },
496
+ };
497
+ export default async (ctx) => {
498
+ // ctx.input: provider, sourceId, messageId, threadId, receivedAt.
499
+ // Read the message/attachments through the declared integration operations.
500
+ // Persist business progress in the app's own workflow records.
501
+ };
502
+ ```
503
+
504
+ `connection` names the configured Gmail or Outlook connection. A literal `filter.from`
505
+ may additionally require an exact sender. Deployment registers the job; activation requires
506
+ the app owner's approved read access and background consent for that environment.
507
+ Multiple apps share one provider watch per mailbox. Notifications enqueue the existing durable
508
+ job runtime; duplicate deliveries retain the same run instead of rerunning a completed job.
509
+ This does not add a per-app polling timer. Cluster hosting and cross-account delivery are not
510
+ supported by this first release. Local manual job execution tests the handler, not the provider
511
+ subscription; verify a real incoming email after deployment before calling the trigger live.
package/dist/index.d.ts CHANGED
@@ -1093,7 +1093,79 @@ export interface ServiceRequestResult {
1093
1093
  headers: Record<string, string>;
1094
1094
  body: unknown;
1095
1095
  }
1096
- export type IntegrationRequest = ServiceRequest | SlackChannelHistoryRequest | SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest | SlackDmSendRequest | SlackUserLookupRequest | WarehouseViewReadRequest | WarehouseTableAppendRequest | PhoneSmsListRequest | PhoneNumberGetRequest | GmailThreadListRequest | GmailThreadReadRequest | GmailMessageReadRequest | GmailAttachmentReadRequest | GmailMessageSendRequest | BasecampProjectListRequest | BasecampTodolistListRequest | BasecampTodoListRequest | BasecampTodoCreateRequest | BasecampTodoCompleteRequest | BasecampMessageListRequest | BasecampMessagePostRequest | BasecampCommentListRequest | BasecampCommentPostRequest | BasecampCampfirePostRequest | BasecampPeopleListRequest | SharePointListListRequest | SharePointItemListRequest | SharePointItemCreateRequest | SharePointItemUpdateRequest | SharePointDriveListRequest | SharePointFileListRequest | SharePointFileSearchRequest | SharePointFileReadRequest | SharePointFileUploadRequest | CalendarCalendarListRequest | CalendarEventListRequest | CalendarEventReadRequest | CalendarEventCreateRequest | CalendarEventUpdateRequest | CalendarEventCancelRequest | DriveFileListRequest | DriveFileSearchRequest | DriveFileReadRequest | DriveFileUploadRequest | DriveFolderCreateRequest | DocsDocumentReadRequest | DocsDocumentCreateRequest | DocsDocumentAppendRequest | DocsTextReplaceRequest | SheetsSpreadsheetReadRequest | SheetsValuesReadRequest | SheetsValuesAppendRequest | SheetsValuesUpdateRequest | SheetsValuesClearRequest | SheetsSpreadsheetCreateRequest | OutlookMessageListRequest | OutlookMessageReadRequest | OutlookAttachmentReadRequest | OutlookMessageSendRequest | OutlookMessageReplyRequest | OutlookEventListRequest | OutlookEventReadRequest | OutlookEventCreateRequest | OutlookEventUpdateRequest | OutlookEventCancelRequest | OneDriveFileListRequest | OneDriveFileSearchRequest | OneDriveFileReadRequest | OneDriveFileUploadRequest | OneDriveFolderCreateRequest | ExcelWorksheetListRequest | ExcelRangeReadRequest | ExcelRangeUpdateRequest | ExcelTableListRequest | ExcelRowAddRequest | ExcelWorksheetAddRequest | TeamsMessageListRequest | TeamsMessagePostRequest | TeamsReplyPostRequest;
1096
+ export interface BooksLineInput {
1097
+ itemId: string;
1098
+ quantity: string;
1099
+ rate: string;
1100
+ taxId?: string;
1101
+ description?: string;
1102
+ }
1103
+ export interface BooksDocument {
1104
+ id: string;
1105
+ number: string;
1106
+ status: string;
1107
+ currency: string;
1108
+ total: string;
1109
+ vendorId?: string;
1110
+ customerId?: string;
1111
+ lineItems: {
1112
+ itemId: string;
1113
+ quantity: string;
1114
+ rate: string;
1115
+ total: string;
1116
+ description: string;
1117
+ }[];
1118
+ }
1119
+ export type ZohoBooksRequest = {
1120
+ operation: 'zohobooks.contact.list';
1121
+ resource: 'organization';
1122
+ input?: {
1123
+ q?: string;
1124
+ pageToken?: string;
1125
+ };
1126
+ } | {
1127
+ operation: 'zohobooks.purchaseorder.list';
1128
+ resource: 'organization';
1129
+ input: {
1130
+ vendorId: string;
1131
+ pageToken?: string;
1132
+ };
1133
+ } | {
1134
+ operation: 'zohobooks.purchaseorder.read';
1135
+ resource: 'organization';
1136
+ input: {
1137
+ purchaseOrderId: string;
1138
+ };
1139
+ } | {
1140
+ operation: 'zohobooks.bill.read' | 'zohobooks.invoice.read' | 'zohobooks.invoice.pdf';
1141
+ resource: 'organization';
1142
+ input: {
1143
+ documentId: string;
1144
+ };
1145
+ } | {
1146
+ operation: 'zohobooks.bill.create';
1147
+ resource: 'organization';
1148
+ idempotencyKey: string;
1149
+ input: {
1150
+ vendorId: string;
1151
+ documentNumber: string;
1152
+ documentDate: string;
1153
+ purchaseOrderId?: string;
1154
+ lineItems: BooksLineInput[];
1155
+ };
1156
+ } | {
1157
+ operation: 'zohobooks.invoice.create';
1158
+ resource: 'organization';
1159
+ idempotencyKey: string;
1160
+ input: {
1161
+ customerId: string;
1162
+ documentDate: string;
1163
+ documentNumber?: string;
1164
+ referenceNumber?: string;
1165
+ lineItems: BooksLineInput[];
1166
+ };
1167
+ };
1168
+ export type IntegrationRequest = ZohoBooksRequest | ServiceRequest | SlackChannelHistoryRequest | SlackMessagePostRequest | SlackMessageUpdateRequest | SlackMessageDeleteRequest | SlackDmSendRequest | SlackUserLookupRequest | WarehouseViewReadRequest | WarehouseTableAppendRequest | PhoneSmsListRequest | PhoneNumberGetRequest | GmailThreadListRequest | GmailThreadReadRequest | GmailMessageReadRequest | GmailAttachmentReadRequest | GmailMessageSendRequest | BasecampProjectListRequest | BasecampTodolistListRequest | BasecampTodoListRequest | BasecampTodoCreateRequest | BasecampTodoCompleteRequest | BasecampMessageListRequest | BasecampMessagePostRequest | BasecampCommentListRequest | BasecampCommentPostRequest | BasecampCampfirePostRequest | BasecampPeopleListRequest | SharePointListListRequest | SharePointItemListRequest | SharePointItemCreateRequest | SharePointItemUpdateRequest | SharePointDriveListRequest | SharePointFileListRequest | SharePointFileSearchRequest | SharePointFileReadRequest | SharePointFileUploadRequest | CalendarCalendarListRequest | CalendarEventListRequest | CalendarEventReadRequest | CalendarEventCreateRequest | CalendarEventUpdateRequest | CalendarEventCancelRequest | DriveFileListRequest | DriveFileSearchRequest | DriveFileReadRequest | DriveFileUploadRequest | DriveFolderCreateRequest | DocsDocumentReadRequest | DocsDocumentCreateRequest | DocsDocumentAppendRequest | DocsTextReplaceRequest | SheetsSpreadsheetReadRequest | SheetsValuesReadRequest | SheetsValuesAppendRequest | SheetsValuesUpdateRequest | SheetsValuesClearRequest | SheetsSpreadsheetCreateRequest | OutlookMessageListRequest | OutlookMessageReadRequest | OutlookAttachmentReadRequest | OutlookMessageSendRequest | OutlookMessageReplyRequest | OutlookEventListRequest | OutlookEventReadRequest | OutlookEventCreateRequest | OutlookEventUpdateRequest | OutlookEventCancelRequest | OneDriveFileListRequest | OneDriveFileSearchRequest | OneDriveFileReadRequest | OneDriveFileUploadRequest | OneDriveFolderCreateRequest | ExcelWorksheetListRequest | ExcelRangeReadRequest | ExcelRangeUpdateRequest | ExcelTableListRequest | ExcelRowAddRequest | ExcelWorksheetAddRequest | TeamsMessageListRequest | TeamsMessagePostRequest | TeamsReplyPostRequest;
1097
1169
  export type IntegrationOperation = IntegrationRequest['operation'];
1098
1170
  export interface SlackMessage {
1099
1171
  id: string;
@@ -1585,7 +1657,31 @@ export interface TeamsMessagePostResult {
1585
1657
  id: string;
1586
1658
  webUrl: string;
1587
1659
  }
1588
- 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 WarehouseTableAppendRequest ? WarehouseTableAppendResult : R extends PhoneSmsListRequest ? PhoneSmsListResult : R extends PhoneNumberGetRequest ? PhoneNumberGetResult : R extends GmailThreadListRequest ? GmailThreadListResult : R extends GmailThreadReadRequest ? GmailThreadReadResult : R extends GmailMessageReadRequest ? GmailMessageReadResult : R extends GmailAttachmentReadRequest ? GmailAttachmentReadResult : R extends GmailMessageSendRequest ? GmailMessageSendResult : R extends BasecampProjectListRequest ? BasecampProjectListResult : R extends BasecampTodolistListRequest ? BasecampTodolistListResult : R extends BasecampTodoListRequest ? BasecampTodoListResult : R extends BasecampMessageListRequest ? BasecampMessageListResult : R extends BasecampCommentListRequest ? BasecampCommentListResult : R extends BasecampPeopleListRequest ? BasecampPeopleListResult : R extends BasecampTodoCreateRequest | BasecampMessagePostRequest | BasecampCommentPostRequest | BasecampCampfirePostRequest ? BasecampRecordingResult : R extends BasecampTodoCompleteRequest ? BasecampTodoCompleteResult : R extends SharePointListListRequest ? SharePointListListResult : R extends SharePointItemListRequest ? SharePointItemListResult : R extends SharePointItemCreateRequest ? SharePointItemCreateResult : R extends SharePointItemUpdateRequest ? SharePointItemUpdateResult : R extends SharePointDriveListRequest ? SharePointDriveListResult : R extends SharePointFileListRequest | SharePointFileSearchRequest ? SharePointFileListResult : R extends SharePointFileReadRequest ? SharePointFileReadResult : R extends SharePointFileUploadRequest ? SharePointFileUploadResult : R extends CalendarCalendarListRequest ? CalendarCalendarListResult : R extends CalendarEventListRequest | OutlookEventListRequest ? CalendarEventListResult : R extends CalendarEventReadRequest | OutlookEventReadRequest ? CalendarEventReadResult : R extends CalendarEventCreateRequest | OutlookEventCreateRequest ? CalendarEventCreateResult : R extends CalendarEventUpdateRequest | CalendarEventCancelRequest | OutlookEventUpdateRequest | OutlookEventCancelRequest ? CalendarEventUpdateResult : R extends DriveFileListRequest | DriveFileSearchRequest | OneDriveFileListRequest | OneDriveFileSearchRequest ? DriveFileListResult : R extends DriveFileReadRequest | OneDriveFileReadRequest ? DriveFileReadResult : R extends DriveFileUploadRequest | OneDriveFileUploadRequest ? DriveFileUploadResult : R extends DriveFolderCreateRequest | OneDriveFolderCreateRequest ? DriveFolderCreateResult : R extends DocsDocumentReadRequest ? DocsDocumentReadResult : R extends DocsDocumentCreateRequest ? DocsDocumentCreateResult : R extends DocsDocumentAppendRequest ? DocsDocumentAppendResult : R extends DocsTextReplaceRequest ? DocsTextReplaceResult : R extends SheetsSpreadsheetReadRequest ? SheetsSpreadsheetReadResult : R extends SheetsValuesReadRequest ? SheetsValuesReadResult : R extends SheetsValuesAppendRequest ? SheetsValuesAppendResult : R extends SheetsValuesUpdateRequest ? SheetsValuesUpdateResult : R extends SheetsValuesClearRequest ? SheetsValuesClearResult : R extends SheetsSpreadsheetCreateRequest ? SheetsSpreadsheetCreateResult : R extends OutlookMessageListRequest ? OutlookMessageListResult : R extends OutlookMessageReadRequest ? OutlookMessageReadResult : R extends OutlookAttachmentReadRequest ? OutlookAttachmentReadResult : R extends OutlookMessageSendRequest | OutlookMessageReplyRequest ? OutlookMessageSendResult : R extends ExcelWorksheetListRequest ? ExcelWorksheetListResult : R extends ExcelRangeReadRequest ? ExcelRangeReadResult : R extends ExcelRangeUpdateRequest ? ExcelRangeUpdateResult : R extends ExcelTableListRequest ? ExcelTableListResult : R extends ExcelRowAddRequest ? ExcelRowAddResult : R extends ExcelWorksheetAddRequest ? ExcelWorksheetAddResult : R extends TeamsMessageListRequest ? TeamsMessageListResult : R extends TeamsMessagePostRequest | TeamsReplyPostRequest ? TeamsMessagePostResult : never;
1660
+ export type IntegrationResult<R extends IntegrationRequest> = R extends {
1661
+ operation: 'zohobooks.contact.list';
1662
+ } ? {
1663
+ booksContacts: {
1664
+ id: string;
1665
+ name: string;
1666
+ type: string;
1667
+ }[];
1668
+ hasMore: boolean;
1669
+ nextPageToken?: string;
1670
+ } : R extends {
1671
+ operation: 'zohobooks.purchaseorder.list';
1672
+ } ? {
1673
+ booksPurchaseOrders: BooksDocument[];
1674
+ hasMore: boolean;
1675
+ nextPageToken?: string;
1676
+ } : R extends {
1677
+ operation: 'zohobooks.invoice.pdf';
1678
+ } ? GmailAttachmentReadResult : R extends {
1679
+ operation: 'zohobooks.bill.create' | 'zohobooks.invoice.create';
1680
+ } ? {
1681
+ id: string;
1682
+ } : R extends ZohoBooksRequest ? {
1683
+ booksDocument: BooksDocument;
1684
+ } : 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 WarehouseTableAppendRequest ? WarehouseTableAppendResult : R extends PhoneSmsListRequest ? PhoneSmsListResult : R extends PhoneNumberGetRequest ? PhoneNumberGetResult : R extends GmailThreadListRequest ? GmailThreadListResult : R extends GmailThreadReadRequest ? GmailThreadReadResult : R extends GmailMessageReadRequest ? GmailMessageReadResult : R extends GmailAttachmentReadRequest ? GmailAttachmentReadResult : R extends GmailMessageSendRequest ? GmailMessageSendResult : R extends BasecampProjectListRequest ? BasecampProjectListResult : R extends BasecampTodolistListRequest ? BasecampTodolistListResult : R extends BasecampTodoListRequest ? BasecampTodoListResult : R extends BasecampMessageListRequest ? BasecampMessageListResult : R extends BasecampCommentListRequest ? BasecampCommentListResult : R extends BasecampPeopleListRequest ? BasecampPeopleListResult : R extends BasecampTodoCreateRequest | BasecampMessagePostRequest | BasecampCommentPostRequest | BasecampCampfirePostRequest ? BasecampRecordingResult : R extends BasecampTodoCompleteRequest ? BasecampTodoCompleteResult : R extends SharePointListListRequest ? SharePointListListResult : R extends SharePointItemListRequest ? SharePointItemListResult : R extends SharePointItemCreateRequest ? SharePointItemCreateResult : R extends SharePointItemUpdateRequest ? SharePointItemUpdateResult : R extends SharePointDriveListRequest ? SharePointDriveListResult : R extends SharePointFileListRequest | SharePointFileSearchRequest ? SharePointFileListResult : R extends SharePointFileReadRequest ? SharePointFileReadResult : R extends SharePointFileUploadRequest ? SharePointFileUploadResult : R extends CalendarCalendarListRequest ? CalendarCalendarListResult : R extends CalendarEventListRequest | OutlookEventListRequest ? CalendarEventListResult : R extends CalendarEventReadRequest | OutlookEventReadRequest ? CalendarEventReadResult : R extends CalendarEventCreateRequest | OutlookEventCreateRequest ? CalendarEventCreateResult : R extends CalendarEventUpdateRequest | CalendarEventCancelRequest | OutlookEventUpdateRequest | OutlookEventCancelRequest ? CalendarEventUpdateResult : R extends DriveFileListRequest | DriveFileSearchRequest | OneDriveFileListRequest | OneDriveFileSearchRequest ? DriveFileListResult : R extends DriveFileReadRequest | OneDriveFileReadRequest ? DriveFileReadResult : R extends DriveFileUploadRequest | OneDriveFileUploadRequest ? DriveFileUploadResult : R extends DriveFolderCreateRequest | OneDriveFolderCreateRequest ? DriveFolderCreateResult : R extends DocsDocumentReadRequest ? DocsDocumentReadResult : R extends DocsDocumentCreateRequest ? DocsDocumentCreateResult : R extends DocsDocumentAppendRequest ? DocsDocumentAppendResult : R extends DocsTextReplaceRequest ? DocsTextReplaceResult : R extends SheetsSpreadsheetReadRequest ? SheetsSpreadsheetReadResult : R extends SheetsValuesReadRequest ? SheetsValuesReadResult : R extends SheetsValuesAppendRequest ? SheetsValuesAppendResult : R extends SheetsValuesUpdateRequest ? SheetsValuesUpdateResult : R extends SheetsValuesClearRequest ? SheetsValuesClearResult : R extends SheetsSpreadsheetCreateRequest ? SheetsSpreadsheetCreateResult : R extends OutlookMessageListRequest ? OutlookMessageListResult : R extends OutlookMessageReadRequest ? OutlookMessageReadResult : R extends OutlookAttachmentReadRequest ? OutlookAttachmentReadResult : R extends OutlookMessageSendRequest | OutlookMessageReplyRequest ? OutlookMessageSendResult : R extends ExcelWorksheetListRequest ? ExcelWorksheetListResult : R extends ExcelRangeReadRequest ? ExcelRangeReadResult : R extends ExcelRangeUpdateRequest ? ExcelRangeUpdateResult : R extends ExcelTableListRequest ? ExcelTableListResult : R extends ExcelRowAddRequest ? ExcelRowAddResult : R extends ExcelWorksheetAddRequest ? ExcelWorksheetAddResult : R extends TeamsMessageListRequest ? TeamsMessageListResult : R extends TeamsMessagePostRequest | TeamsReplyPostRequest ? TeamsMessagePostResult : never;
1589
1685
  export type IntegrationConnectResult = {
1590
1686
  status: 'connected';
1591
1687
  accountLabel: string;
@@ -2151,6 +2151,236 @@ export declare const IntegrationExecuteInputRules: {
2151
2151
  };
2152
2152
  };
2153
2153
  };
2154
+ readonly "zohobooks.contact.list": {
2155
+ readonly idempotencyKey: false;
2156
+ readonly fields: {
2157
+ readonly q: {
2158
+ readonly kind: "string";
2159
+ readonly required: false;
2160
+ readonly minLength: 1;
2161
+ readonly maxLength: 256;
2162
+ };
2163
+ readonly pageToken: {
2164
+ readonly kind: "string";
2165
+ readonly required: false;
2166
+ readonly minLength: 1;
2167
+ readonly maxLength: 2048;
2168
+ };
2169
+ };
2170
+ };
2171
+ readonly "zohobooks.purchaseorder.list": {
2172
+ readonly idempotencyKey: false;
2173
+ readonly fields: {
2174
+ readonly vendorId: {
2175
+ readonly kind: "string";
2176
+ readonly required: true;
2177
+ readonly minLength: 0;
2178
+ readonly maxLength: 32;
2179
+ readonly pattern: "^[0-9]{1,32}$";
2180
+ };
2181
+ readonly pageToken: {
2182
+ readonly kind: "string";
2183
+ readonly required: false;
2184
+ readonly minLength: 1;
2185
+ readonly maxLength: 2048;
2186
+ };
2187
+ };
2188
+ };
2189
+ readonly "zohobooks.purchaseorder.read": {
2190
+ readonly idempotencyKey: false;
2191
+ readonly fields: {
2192
+ readonly purchaseOrderId: {
2193
+ readonly kind: "string";
2194
+ readonly required: true;
2195
+ readonly minLength: 0;
2196
+ readonly maxLength: 32;
2197
+ readonly pattern: "^[0-9]{1,32}$";
2198
+ };
2199
+ };
2200
+ };
2201
+ readonly "zohobooks.bill.create": {
2202
+ readonly idempotencyKey: true;
2203
+ readonly fields: {
2204
+ readonly vendorId: {
2205
+ readonly kind: "string";
2206
+ readonly required: true;
2207
+ readonly minLength: 0;
2208
+ readonly maxLength: 32;
2209
+ readonly pattern: "^[0-9]{1,32}$";
2210
+ };
2211
+ readonly documentNumber: {
2212
+ readonly kind: "string";
2213
+ readonly required: true;
2214
+ readonly minLength: 1;
2215
+ readonly maxLength: 100;
2216
+ };
2217
+ readonly documentDate: {
2218
+ readonly kind: "string";
2219
+ readonly required: true;
2220
+ readonly minLength: 0;
2221
+ readonly maxLength: 10;
2222
+ readonly pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$";
2223
+ };
2224
+ readonly purchaseOrderId: {
2225
+ readonly kind: "string";
2226
+ readonly required: false;
2227
+ readonly minLength: 0;
2228
+ readonly maxLength: 32;
2229
+ readonly pattern: "^[0-9]{1,32}$";
2230
+ };
2231
+ readonly lineItems: {
2232
+ readonly kind: "object[]";
2233
+ readonly required: true;
2234
+ readonly minItems: 1;
2235
+ readonly maxItems: 100;
2236
+ readonly fields: {
2237
+ readonly itemId: {
2238
+ readonly kind: "string";
2239
+ readonly required: true;
2240
+ readonly minLength: 0;
2241
+ readonly maxLength: 32;
2242
+ readonly pattern: "^[0-9]{1,32}$";
2243
+ };
2244
+ readonly quantity: {
2245
+ readonly kind: "string";
2246
+ readonly required: true;
2247
+ readonly minLength: 0;
2248
+ readonly maxLength: 17;
2249
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2250
+ };
2251
+ readonly rate: {
2252
+ readonly kind: "string";
2253
+ readonly required: true;
2254
+ readonly minLength: 0;
2255
+ readonly maxLength: 17;
2256
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2257
+ };
2258
+ readonly taxId: {
2259
+ readonly kind: "string";
2260
+ readonly required: false;
2261
+ readonly minLength: 0;
2262
+ readonly maxLength: 32;
2263
+ readonly pattern: "^[0-9]{1,32}$";
2264
+ };
2265
+ readonly description: {
2266
+ readonly kind: "string";
2267
+ readonly required: false;
2268
+ readonly minLength: 0;
2269
+ readonly maxLength: 2000;
2270
+ };
2271
+ };
2272
+ };
2273
+ };
2274
+ };
2275
+ readonly "zohobooks.bill.read": {
2276
+ readonly idempotencyKey: false;
2277
+ readonly fields: {
2278
+ readonly documentId: {
2279
+ readonly kind: "string";
2280
+ readonly required: true;
2281
+ readonly minLength: 0;
2282
+ readonly maxLength: 32;
2283
+ readonly pattern: "^[0-9]{1,32}$";
2284
+ };
2285
+ };
2286
+ };
2287
+ readonly "zohobooks.invoice.create": {
2288
+ readonly idempotencyKey: true;
2289
+ readonly fields: {
2290
+ readonly customerId: {
2291
+ readonly kind: "string";
2292
+ readonly required: true;
2293
+ readonly minLength: 0;
2294
+ readonly maxLength: 32;
2295
+ readonly pattern: "^[0-9]{1,32}$";
2296
+ };
2297
+ readonly documentNumber: {
2298
+ readonly kind: "string";
2299
+ readonly required: false;
2300
+ readonly minLength: 1;
2301
+ readonly maxLength: 100;
2302
+ };
2303
+ readonly documentDate: {
2304
+ readonly kind: "string";
2305
+ readonly required: true;
2306
+ readonly minLength: 0;
2307
+ readonly maxLength: 10;
2308
+ readonly pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$";
2309
+ };
2310
+ readonly referenceNumber: {
2311
+ readonly kind: "string";
2312
+ readonly required: false;
2313
+ readonly minLength: 1;
2314
+ readonly maxLength: 100;
2315
+ };
2316
+ readonly lineItems: {
2317
+ readonly kind: "object[]";
2318
+ readonly required: true;
2319
+ readonly minItems: 1;
2320
+ readonly maxItems: 100;
2321
+ readonly fields: {
2322
+ readonly itemId: {
2323
+ readonly kind: "string";
2324
+ readonly required: true;
2325
+ readonly minLength: 0;
2326
+ readonly maxLength: 32;
2327
+ readonly pattern: "^[0-9]{1,32}$";
2328
+ };
2329
+ readonly quantity: {
2330
+ readonly kind: "string";
2331
+ readonly required: true;
2332
+ readonly minLength: 0;
2333
+ readonly maxLength: 17;
2334
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2335
+ };
2336
+ readonly rate: {
2337
+ readonly kind: "string";
2338
+ readonly required: true;
2339
+ readonly minLength: 0;
2340
+ readonly maxLength: 17;
2341
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2342
+ };
2343
+ readonly taxId: {
2344
+ readonly kind: "string";
2345
+ readonly required: false;
2346
+ readonly minLength: 0;
2347
+ readonly maxLength: 32;
2348
+ readonly pattern: "^[0-9]{1,32}$";
2349
+ };
2350
+ readonly description: {
2351
+ readonly kind: "string";
2352
+ readonly required: false;
2353
+ readonly minLength: 0;
2354
+ readonly maxLength: 2000;
2355
+ };
2356
+ };
2357
+ };
2358
+ };
2359
+ };
2360
+ readonly "zohobooks.invoice.read": {
2361
+ readonly idempotencyKey: false;
2362
+ readonly fields: {
2363
+ readonly documentId: {
2364
+ readonly kind: "string";
2365
+ readonly required: true;
2366
+ readonly minLength: 0;
2367
+ readonly maxLength: 32;
2368
+ readonly pattern: "^[0-9]{1,32}$";
2369
+ };
2370
+ };
2371
+ };
2372
+ readonly "zohobooks.invoice.pdf": {
2373
+ readonly idempotencyKey: false;
2374
+ readonly fields: {
2375
+ readonly documentId: {
2376
+ readonly kind: "string";
2377
+ readonly required: true;
2378
+ readonly minLength: 0;
2379
+ readonly maxLength: 32;
2380
+ readonly pattern: "^[0-9]{1,32}$";
2381
+ };
2382
+ };
2383
+ };
2154
2384
  };
2155
2385
  export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
2156
2386
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
@@ -668,6 +668,76 @@ export const IntegrationExecuteInputRules = {
668
668
  body: { kind: "object", required: false },
669
669
  },
670
670
  },
671
+ "zohobooks.contact.list": {
672
+ idempotencyKey: false,
673
+ fields: {
674
+ q: { kind: "string", required: false, minLength: 1, maxLength: 256 },
675
+ pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
676
+ },
677
+ },
678
+ "zohobooks.purchaseorder.list": {
679
+ idempotencyKey: false,
680
+ fields: {
681
+ vendorId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
682
+ pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
683
+ },
684
+ },
685
+ "zohobooks.purchaseorder.read": {
686
+ idempotencyKey: false,
687
+ fields: {
688
+ purchaseOrderId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
689
+ },
690
+ },
691
+ "zohobooks.bill.create": {
692
+ idempotencyKey: true,
693
+ fields: {
694
+ vendorId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
695
+ documentNumber: { kind: "string", required: true, minLength: 1, maxLength: 100 },
696
+ documentDate: { kind: "string", required: true, minLength: 0, maxLength: 10, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
697
+ purchaseOrderId: { kind: "string", required: false, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
698
+ lineItems: { kind: "object[]", required: true, minItems: 1, maxItems: 100, fields: {
699
+ itemId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
700
+ quantity: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
701
+ rate: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
702
+ taxId: { kind: "string", required: false, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
703
+ description: { kind: "string", required: false, minLength: 0, maxLength: 2000 },
704
+ } },
705
+ },
706
+ },
707
+ "zohobooks.bill.read": {
708
+ idempotencyKey: false,
709
+ fields: {
710
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
711
+ },
712
+ },
713
+ "zohobooks.invoice.create": {
714
+ idempotencyKey: true,
715
+ fields: {
716
+ customerId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
717
+ documentNumber: { kind: "string", required: false, minLength: 1, maxLength: 100 },
718
+ documentDate: { kind: "string", required: true, minLength: 0, maxLength: 10, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
719
+ referenceNumber: { kind: "string", required: false, minLength: 1, maxLength: 100 },
720
+ lineItems: { kind: "object[]", required: true, minItems: 1, maxItems: 100, fields: {
721
+ itemId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
722
+ quantity: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
723
+ rate: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
724
+ taxId: { kind: "string", required: false, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
725
+ description: { kind: "string", required: false, minLength: 0, maxLength: 2000 },
726
+ } },
727
+ },
728
+ },
729
+ "zohobooks.invoice.read": {
730
+ idempotencyKey: false,
731
+ fields: {
732
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
733
+ },
734
+ },
735
+ "zohobooks.invoice.pdf": {
736
+ idempotencyKey: false,
737
+ fields: {
738
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
739
+ },
740
+ },
671
741
  };
672
742
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
673
743
  export function integrationExecuteInputMaximum(operation, field) {
@@ -567,4 +567,60 @@ export declare const IntegrationOperations: {
567
567
  readonly declaredMode: false;
568
568
  readonly idempotencyKey: false;
569
569
  };
570
+ readonly "zohobooks.contact.list": {
571
+ readonly kind: "saas";
572
+ readonly identities: readonly ["user"];
573
+ readonly readOnly: true;
574
+ readonly declaredMode: false;
575
+ readonly idempotencyKey: false;
576
+ };
577
+ readonly "zohobooks.purchaseorder.list": {
578
+ readonly kind: "saas";
579
+ readonly identities: readonly ["user"];
580
+ readonly readOnly: true;
581
+ readonly declaredMode: false;
582
+ readonly idempotencyKey: false;
583
+ };
584
+ readonly "zohobooks.purchaseorder.read": {
585
+ readonly kind: "saas";
586
+ readonly identities: readonly ["user"];
587
+ readonly readOnly: true;
588
+ readonly declaredMode: false;
589
+ readonly idempotencyKey: false;
590
+ };
591
+ readonly "zohobooks.bill.create": {
592
+ readonly kind: "saas";
593
+ readonly identities: readonly ["user"];
594
+ readonly readOnly: false;
595
+ readonly declaredMode: false;
596
+ readonly idempotencyKey: true;
597
+ };
598
+ readonly "zohobooks.bill.read": {
599
+ readonly kind: "saas";
600
+ readonly identities: readonly ["user"];
601
+ readonly readOnly: true;
602
+ readonly declaredMode: false;
603
+ readonly idempotencyKey: false;
604
+ };
605
+ readonly "zohobooks.invoice.create": {
606
+ readonly kind: "saas";
607
+ readonly identities: readonly ["user"];
608
+ readonly readOnly: false;
609
+ readonly declaredMode: false;
610
+ readonly idempotencyKey: true;
611
+ };
612
+ readonly "zohobooks.invoice.read": {
613
+ readonly kind: "saas";
614
+ readonly identities: readonly ["user"];
615
+ readonly readOnly: true;
616
+ readonly declaredMode: false;
617
+ readonly idempotencyKey: false;
618
+ };
619
+ readonly "zohobooks.invoice.pdf": {
620
+ readonly kind: "saas";
621
+ readonly identities: readonly ["user"];
622
+ readonly readOnly: true;
623
+ readonly declaredMode: false;
624
+ readonly idempotencyKey: false;
625
+ };
570
626
  };
@@ -734,5 +734,77 @@ export const IntegrationOperations = {
734
734
  "readOnly": false,
735
735
  "declaredMode": false,
736
736
  "idempotencyKey": false
737
+ },
738
+ "zohobooks.contact.list": {
739
+ "kind": "saas",
740
+ "identities": [
741
+ "user"
742
+ ],
743
+ "readOnly": true,
744
+ "declaredMode": false,
745
+ "idempotencyKey": false
746
+ },
747
+ "zohobooks.purchaseorder.list": {
748
+ "kind": "saas",
749
+ "identities": [
750
+ "user"
751
+ ],
752
+ "readOnly": true,
753
+ "declaredMode": false,
754
+ "idempotencyKey": false
755
+ },
756
+ "zohobooks.purchaseorder.read": {
757
+ "kind": "saas",
758
+ "identities": [
759
+ "user"
760
+ ],
761
+ "readOnly": true,
762
+ "declaredMode": false,
763
+ "idempotencyKey": false
764
+ },
765
+ "zohobooks.bill.create": {
766
+ "kind": "saas",
767
+ "identities": [
768
+ "user"
769
+ ],
770
+ "readOnly": false,
771
+ "declaredMode": false,
772
+ "idempotencyKey": true
773
+ },
774
+ "zohobooks.bill.read": {
775
+ "kind": "saas",
776
+ "identities": [
777
+ "user"
778
+ ],
779
+ "readOnly": true,
780
+ "declaredMode": false,
781
+ "idempotencyKey": false
782
+ },
783
+ "zohobooks.invoice.create": {
784
+ "kind": "saas",
785
+ "identities": [
786
+ "user"
787
+ ],
788
+ "readOnly": false,
789
+ "declaredMode": false,
790
+ "idempotencyKey": true
791
+ },
792
+ "zohobooks.invoice.read": {
793
+ "kind": "saas",
794
+ "identities": [
795
+ "user"
796
+ ],
797
+ "readOnly": true,
798
+ "declaredMode": false,
799
+ "idempotencyKey": false
800
+ },
801
+ "zohobooks.invoice.pdf": {
802
+ "kind": "saas",
803
+ "identities": [
804
+ "user"
805
+ ],
806
+ "readOnly": true,
807
+ "declaredMode": false,
808
+ "idempotencyKey": false
737
809
  }
738
810
  };
package/dist/jobs.d.ts CHANGED
@@ -1,8 +1,19 @@
1
1
  export declare const JOBS_DIR = "jobs";
2
+ export interface EmailArrivalTrigger {
3
+ type: 'email.arrived';
4
+ connection: string;
5
+ resource: 'inbox';
6
+ filter?: {
7
+ from?: string;
8
+ subjectContains?: string;
9
+ };
10
+ }
2
11
  export interface JobDefinition {
3
12
  name: string;
4
13
  schedule?: string;
14
+ trigger?: EmailArrivalTrigger;
5
15
  }
16
+ export declare function emailArrivalTrigger(value: unknown): EmailArrivalTrigger;
6
17
  export interface JobContext {
7
18
  scheduledAt: string;
8
19
  runId?: string;
package/dist/jobs.js CHANGED
@@ -6,13 +6,40 @@ import { stripTypeScriptTypes } from 'node:module';
6
6
  import { parse } from 'acorn';
7
7
  export const JOBS_DIR = 'jobs';
8
8
  const LOGICAL_NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
9
- function bindsSchedule(binding) {
9
+ function literalObject(node) {
10
+ const value = node;
11
+ if (value?.type === 'Literal')
12
+ return value.value;
13
+ if (value?.type !== 'ObjectExpression')
14
+ throw new Error('trigger must be a literal object');
15
+ const result = {};
16
+ for (const property of value.properties ?? []) {
17
+ if (property.type !== 'Property' || property.computed || property.kind !== 'init' || property.method)
18
+ throw new Error('trigger must be a literal object');
19
+ const key = property.key.name ?? property.key.value;
20
+ if (typeof key !== 'string' || Object.hasOwn(result, key))
21
+ throw new Error('trigger contains an invalid or repeated field');
22
+ Object.defineProperty(result, key, { value: literalObject(property.value), enumerable: true });
23
+ }
24
+ return result;
25
+ }
26
+ export function emailArrivalTrigger(value) {
27
+ const trigger = value;
28
+ if (!trigger || typeof trigger !== 'object' || Object.keys(trigger).some(key => !['type', 'connection', 'resource', 'filter'].includes(key))
29
+ || trigger.type !== 'email.arrived' || trigger.resource !== 'inbox' || typeof trigger.connection !== 'string' || !LOGICAL_NAME.test(trigger.connection))
30
+ throw new Error('trigger must declare email.arrived, a connection and resource inbox');
31
+ if (trigger.filter !== undefined && (!trigger.filter || typeof trigger.filter !== 'object' || Array.isArray(trigger.filter)
32
+ || Object.entries(trigger.filter).some(([key, value]) => !['from', 'subjectContains'].includes(key) || typeof value !== 'string' || !value.trim() || value.length > 320)))
33
+ throw new Error('trigger filter supports non-empty from and subjectContains strings up to 320 characters');
34
+ return trigger;
35
+ }
36
+ function bindsJobSetup(binding) {
10
37
  switch (binding.type) {
11
- case 'Identifier': return binding.name === 'schedule';
12
- case 'ObjectPattern': return binding.properties.some(property => bindsSchedule(property.type === 'RestElement' ? property.argument : property.value));
13
- case 'ArrayPattern': return binding.elements.some(element => element !== null && bindsSchedule(element));
14
- case 'AssignmentPattern': return bindsSchedule(binding.left);
15
- case 'RestElement': return bindsSchedule(binding.argument);
38
+ case 'Identifier': return binding.name === 'schedule' || binding.name === 'trigger';
39
+ case 'ObjectPattern': return binding.properties.some(property => bindsJobSetup(property.type === 'RestElement' ? property.argument : property.value));
40
+ case 'ArrayPattern': return binding.elements.some(element => element !== null && bindsJobSetup(element));
41
+ case 'AssignmentPattern': return bindsJobSetup(binding.left);
42
+ case 'RestElement': return bindsJobSetup(binding.argument);
16
43
  default: return false;
17
44
  }
18
45
  }
@@ -57,23 +84,35 @@ export async function discoverJobs(root) {
57
84
  fail(`cannot parse the job module: ${error instanceof Error ? error.message : String(error)}`);
58
85
  }
59
86
  let schedule;
87
+ let trigger;
60
88
  for (const statement of statements) {
61
- if (statement.type === 'ExportAllDeclaration' && statement.exported && (statement.exported.type === 'Identifier' ? statement.exported.name : statement.exported.value) === 'schedule')
62
- fail('export one literal schedule directly with export const schedule.');
89
+ if (statement.type === 'ExportAllDeclaration' && statement.exported && ['schedule', 'trigger'].includes(statement.exported.type === 'Identifier' ? statement.exported.name : String(statement.exported.value)))
90
+ fail('export one literal schedule or trigger directly with export const.');
63
91
  if (statement.type !== 'ExportNamedDeclaration')
64
92
  continue;
65
- if (statement.specifiers.some(specifier => specifier.exported.type === 'Identifier' ? specifier.exported.name === 'schedule' : specifier.exported.value === 'schedule'))
66
- fail('export one literal schedule directly with export const schedule.');
93
+ if (statement.specifiers.some(specifier => ['schedule', 'trigger'].includes(specifier.exported.type === 'Identifier' ? specifier.exported.name : String(specifier.exported.value))))
94
+ fail('export one literal schedule or trigger directly with export const.');
67
95
  if (statement.declaration?.type !== 'VariableDeclaration') {
68
- if (statement.declaration?.id?.name === 'schedule')
69
- fail('export one literal schedule directly with export const schedule.');
96
+ if (['schedule', 'trigger'].includes(statement.declaration?.id?.name ?? ''))
97
+ fail('export one literal schedule or trigger directly with export const.');
70
98
  continue;
71
99
  }
72
100
  for (const declaration of statement.declaration.declarations) {
73
- if (!bindsSchedule(declaration.id))
101
+ if (declaration.id.type === 'Identifier' && declaration.id.name === 'trigger') {
102
+ if (statement.declaration.kind !== 'const')
103
+ fail('export one literal trigger with export const trigger.');
104
+ try {
105
+ trigger = emailArrivalTrigger(literalObject(declaration.init));
106
+ }
107
+ catch (error) {
108
+ fail(String(error));
109
+ }
110
+ continue;
111
+ }
112
+ if (!bindsJobSetup(declaration.id))
74
113
  continue;
75
114
  if (declaration.id.type !== 'Identifier')
76
- fail('export one literal schedule directly with export const schedule.');
115
+ fail('export one literal schedule or trigger directly with export const.');
77
116
  const value = declaration.init;
78
117
  if (statement.declaration.kind !== 'const')
79
118
  fail('export one literal schedule.');
@@ -89,7 +128,9 @@ export async function discoverJobs(root) {
89
128
  }
90
129
  if (!statements.some(statement => statement.type === 'ExportDefaultDeclaration'))
91
130
  fail('export one default job handler.');
92
- jobs.push({ name, ...(schedule ? { schedule } : {}) });
131
+ if (schedule && trigger)
132
+ fail('a job may declare a schedule or an email trigger, not both.');
133
+ jobs.push({ name, ...(schedule ? { schedule } : {}), ...(trigger ? { trigger } : {}) });
93
134
  }
94
135
  return jobs.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
95
136
  }
@@ -18,7 +18,9 @@ function eventRuntime() {
18
18
  return runtime;
19
19
  }
20
20
  export async function handler(event) {
21
- const { job, Records: records } = (event && typeof event === 'object' ? event : {});
21
+ const { job, Records: records, mailArrival } = (event && typeof event === 'object' ? event : {});
22
+ if (mailArrival !== undefined)
23
+ return eventRuntime().acceptMailArrival(mailArrival);
22
24
  if (typeof job === 'string' && job) {
23
25
  if (!(await eventRuntime().runJobOnce(job)))
24
26
  throw new Error(`jobs/${job}.ts did not run to completion; the log above names why`);
package/dist/runtime.d.ts CHANGED
@@ -50,6 +50,14 @@ export declare class Runs {
50
50
  enqueue(job: string, input: unknown, owner: string): Promise<{
51
51
  id: string;
52
52
  }>;
53
+ enqueueMail(delivery: {
54
+ deliveryId: string;
55
+ job: string;
56
+ input: unknown;
57
+ owner: string;
58
+ }): Promise<{
59
+ id: string;
60
+ }>;
53
61
  get(id: string, owner: string): Promise<JobRun | null>;
54
62
  cancel(id: string, owner: string): Promise<JobRun | null>;
55
63
  /** Rows whose lease ran out go back to the queue while attempts remain, else fail; how many there were. */
@@ -133,6 +141,7 @@ export declare class Runtime {
133
141
  * would have been. False when the app has no such job, the gateway's data
134
142
  * path never answered, or the handler threw; the log names which.
135
143
  */
144
+ acceptMailArrival(value: unknown): Promise<void>;
136
145
  runJobOnce(name: string, now?: Date): Promise<boolean>;
137
146
  private awaitDataPath;
138
147
  stop(): Promise<void>;
@@ -152,6 +161,7 @@ export declare class Runtime {
152
161
  private serve;
153
162
  private transaction;
154
163
  private loadFailures;
164
+ private signalQueuedRun;
155
165
  private reserved;
156
166
  }
157
167
  /** The runtime's settings from the environment; exported for the entry point and its test. */
package/dist/runtime.js CHANGED
@@ -56,6 +56,7 @@
56
56
  // ISOMORPH_JOBS_QUEUE_URL an enqueue sends the run to this SQS queue and
57
57
  // the queue loop never runs here: the cell's
58
58
  // Event Function works it (lambda-events-entry.ts)
59
+ import { createHash } from 'node:crypto';
59
60
  import { AsyncLocalStorage } from 'node:async_hooks';
60
61
  import { createServer } from 'node:http';
61
62
  import { readFile, readdir, stat } from 'node:fs/promises';
@@ -203,6 +204,19 @@ export class Runs {
203
204
  const { data } = await this.table().insert({ job, input: input ?? null, status: 'queued', attempt: 0, max_attempts: DEFAULT_MAX_ATTEMPTS, progress: 0, owner_subject: owner, cancel_requested: false }).select('id').single();
204
205
  return { id: data.id };
205
206
  }
207
+ async enqueueMail(delivery) {
208
+ const hex = createHash('sha256').update(`mail:${delivery.deliveryId}`).digest('hex');
209
+ const id = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
210
+ try {
211
+ await this.table().insert({ id, job: delivery.job, input: delivery.input, status: 'queued', attempt: 0, max_attempts: DEFAULT_MAX_ATTEMPTS, progress: 0, owner_subject: delivery.owner, cancel_requested: false });
212
+ }
213
+ catch (error) {
214
+ const existing = await this.get(id, delivery.owner);
215
+ if (!existing || existing.job !== delivery.job)
216
+ throw error;
217
+ }
218
+ return { id };
219
+ }
206
220
  async get(id, owner) {
207
221
  const { data } = await this.table().select('*').eq('id', id).eq('owner_subject', owner).maybeSingle();
208
222
  return data;
@@ -370,6 +384,25 @@ export class Runtime {
370
384
  * would have been. False when the app has no such job, the gateway's data
371
385
  * path never answered, or the handler threw; the log names which.
372
386
  */
387
+ async acceptMailArrival(value) {
388
+ const delivery = value;
389
+ if (!delivery || typeof delivery.deliveryId !== 'string' || !/^[a-f0-9]{64}$/.test(delivery.deliveryId)
390
+ || typeof delivery.owner !== 'string' || !delivery.owner || delivery.owner.length > 320
391
+ || !delivery.input || !['gmail', 'outlook'].includes(delivery.input.provider)
392
+ || typeof delivery.input.messageId !== 'string' || !delivery.input.messageId || delivery.input.messageId.length > 2048
393
+ || typeof delivery.input.threadId !== 'string' || delivery.input.threadId.length > 2048
394
+ || typeof delivery.input.receivedAt !== 'string' || !Number.isFinite(Date.parse(delivery.input.receivedAt)))
395
+ throw new Error('invalid mail arrival');
396
+ this.inventory = await discover(this.options.root);
397
+ const job = this.inventory.jobs.find(job => job.name === delivery.job);
398
+ if (!job?.trigger || job.trigger.connection !== delivery.input.sourceId)
399
+ throw new Error('mail trigger is not declared by this deployed app');
400
+ await this.refreshToken(JOB_SESSION_MIN_VALIDITY_MS);
401
+ if (!this.options.gatewayUrl || !this.options.queueUrl || this.options.jobRuns === false || !(await this.awaitDataPath()))
402
+ throw new Error('mail arrival requires durable job runs');
403
+ const queued = await this.runs.enqueueMail(delivery);
404
+ await this.signalQueuedRun(queued.id);
405
+ }
373
406
  async runJobOnce(name, now = new Date()) {
374
407
  this.inventory = await discover(this.options.root);
375
408
  await this.refreshToken(JOB_SESSION_MIN_VALIDITY_MS);
@@ -674,6 +707,14 @@ export class Runtime {
674
707
  }
675
708
  return failures;
676
709
  }
710
+ async signalQueuedRun(id) {
711
+ if (this.options.queueUrl) {
712
+ const { awsEnvironment, sendQueueMessage } = await import('./aws.js');
713
+ await sendQueueMessage(awsEnvironment(), this.options.queueUrl, JSON.stringify({ runId: id }));
714
+ }
715
+ else
716
+ this.wakeQueue();
717
+ }
677
718
  async reserved(name, input, context) {
678
719
  const body = (input && typeof input === 'object' ? input : {});
679
720
  const owner = context.user.id;
@@ -690,12 +731,7 @@ export class Runtime {
690
731
  throw new IsomorphError('AUTH_REQUIRED', 'a run is started by a signed-in person', undefined, undefined, 401);
691
732
  const queued = await this.runs.enqueue(job, body.input, owner);
692
733
  // Awaited before the enqueue answers: a Web Function may be frozen as soon as it has replied.
693
- if (this.options.queueUrl) {
694
- const { awsEnvironment, sendQueueMessage } = await import('./aws.js');
695
- await sendQueueMessage(awsEnvironment(), this.options.queueUrl, JSON.stringify({ runId: queued.id }));
696
- }
697
- else
698
- this.wakeQueue();
734
+ await this.signalQueuedRun(queued.id);
699
735
  return queued;
700
736
  }
701
737
  case GET_RUN_ACTION: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.26.0",
3
+ "version": "1.28.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",