@isomorph.ai/app-sdk 1.32.0 → 1.34.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
@@ -473,6 +473,32 @@ Without `onProgress` the call is unchanged and `ctx.progress` does nothing. At
473
473
  most 256 steps of up to 1 KiB each reach the page; further steps are dropped.
474
474
  The action still has the 300 s action budget. (Unreleased)
475
475
 
476
+ ## Watching a query: `data.watch`
477
+
478
+ Progress, status and export screens re-read one query on a timer:
479
+
480
+ ```ts
481
+ const stop = isomorph.data.watch(
482
+ () => isomorph.data.from('imports').select('*').eq('id', importId).single(),
483
+ ({ data }) => setImport(data),
484
+ { every: 3000, maxEvery: 15000 }, // the defaults
485
+ );
486
+ // on unmount
487
+ stop();
488
+ ```
489
+
490
+ `build()` runs at once and then every `every` ms (3 s). While the tab is
491
+ hidden no read is made (where there is no `document`, as in Node, nothing is
492
+ skipped). While the answer is unchanged the interval doubles up to `maxEvery`
493
+ (15 s) and goes back to `every` on the first change; every result, changed or
494
+ not, reaches `onResult`. A read that fails (a 401, the network) reaches it
495
+ too, as `{ data: null, error }` with the error `await`ing the query would
496
+ have thrown, and the timer keeps running. There is no new transport or route: each tick is an ordinary
497
+ `QueryBuilder.execute()`, so it counts as app activity and is billed and
498
+ authorized like any read. Watch the job row, not the data table. Use
499
+ `realtime.channel` only when several people edit the same rows and sub-second
500
+ updates matter. (1.32.0)
501
+
476
502
  ## Running on Lambda
477
503
 
478
504
  On a serverless cell Harbour runs an app's server code as Lambda functions, from a `runtime.zip` Kit Build makes; nothing in the app changes. The Web Function starts `dist/lambda-entry.js` (the zip's `index.js` is `import "@isomorph.ai/app-sdk/dist/lambda-entry.js"`), which serves actions on the port in `AWS_LAMBDA_HTTP_ENDPOINT` and stops on SIGTERM. The Event Function's handler is `node_modules/@isomorph.ai/app-sdk/dist/lambda-events-entry.handler`: `{"job": "<name>"}` runs that job once, as `--run-job <name>` does, and SQS records work queued runs until none are left.
package/dist/index.d.ts CHANGED
@@ -39,6 +39,15 @@ export interface IsomorphResult<T> {
39
39
  data: T;
40
40
  count?: number;
41
41
  }
42
+ /** `data.watch` timing: `every` ms between reads (default 3000), backing off to `maxEvery` (default 15000) while the answer does not change. */
43
+ export interface WatchOptions {
44
+ every?: number;
45
+ maxEvery?: number;
46
+ }
47
+ /** What `data.watch` delivers: the read's result, or `{ data: null, error }` when the read failed (the error `await`ing the query would have thrown). */
48
+ export type WatchResult = IsomorphResult<any> & {
49
+ error?: Error;
50
+ };
42
51
  /** How Slack renders a message's `text`: plain text, or Slack mrkdwn (`*bold*`, `<url|label>`, `<@U123>`); plain when absent. */
43
52
  export type SlackTextFormat = 'plain' | 'mrkdwn';
44
53
  /**
@@ -1105,18 +1114,25 @@ export interface BooksLineInput {
1105
1114
  export interface BooksBillLineInput extends BooksLineInput {
1106
1115
  purchaseOrderItemId?: string;
1107
1116
  }
1117
+ export interface BooksInvoiceLineInput extends BooksLineInput {
1118
+ salesOrderItemId?: string;
1119
+ }
1108
1120
  export interface BooksDocument {
1109
1121
  id: string;
1110
1122
  number: string;
1111
1123
  status: string;
1112
1124
  currency: string;
1113
1125
  total: string;
1126
+ referenceNumber?: string;
1114
1127
  vendorId?: string;
1115
1128
  customerId?: string;
1116
1129
  lineItems: {
1117
1130
  itemId: string;
1118
1131
  lineItemId?: string;
1119
1132
  purchaseOrderItemId?: string;
1133
+ sku?: string;
1134
+ taxId?: string;
1135
+ isInvoiced?: boolean;
1120
1136
  quantity: string;
1121
1137
  rate: string;
1122
1138
  total: string;
@@ -1137,6 +1153,43 @@ export type ZohoBooksRequest = {
1137
1153
  vendorId: string;
1138
1154
  pageToken?: string;
1139
1155
  };
1156
+ } | {
1157
+ operation: 'zohobooks.salesorder.list';
1158
+ resource: 'organization';
1159
+ input: {
1160
+ customerId: string;
1161
+ pageToken?: string;
1162
+ };
1163
+ } | {
1164
+ operation: 'zohobooks.salesorder.read' | 'zohobooks.creditnote.read';
1165
+ resource: 'organization';
1166
+ input: {
1167
+ documentId: string;
1168
+ };
1169
+ } | {
1170
+ operation: 'zohobooks.invoice.reviseDraft';
1171
+ resource: 'organization';
1172
+ idempotencyKey: string;
1173
+ input: {
1174
+ documentId: string;
1175
+ lineItems: {
1176
+ lineItemId: string;
1177
+ quantity: string;
1178
+ }[];
1179
+ };
1180
+ } | {
1181
+ operation: 'zohobooks.creditnote.create';
1182
+ resource: 'organization';
1183
+ idempotencyKey: string;
1184
+ input: {
1185
+ documentId: string;
1186
+ documentDate: string;
1187
+ referenceNumber?: string;
1188
+ lineItems: {
1189
+ lineItemId: string;
1190
+ quantity: string;
1191
+ }[];
1192
+ };
1140
1193
  } | {
1141
1194
  operation: 'zohobooks.purchaseorder.read';
1142
1195
  resource: 'organization';
@@ -1169,7 +1222,7 @@ export type ZohoBooksRequest = {
1169
1222
  documentDate: string;
1170
1223
  documentNumber?: string;
1171
1224
  referenceNumber?: string;
1172
- lineItems: BooksLineInput[];
1225
+ lineItems: BooksInvoiceLineInput[];
1173
1226
  };
1174
1227
  };
1175
1228
  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;
@@ -1676,6 +1729,12 @@ export type IntegrationResult<R extends IntegrationRequest> = R extends {
1676
1729
  }[];
1677
1730
  hasMore: boolean;
1678
1731
  nextPageToken?: string;
1732
+ } : R extends {
1733
+ operation: 'zohobooks.salesorder.list';
1734
+ } ? {
1735
+ booksSalesOrders: BooksDocument[];
1736
+ hasMore: boolean;
1737
+ nextPageToken?: string;
1679
1738
  } : R extends {
1680
1739
  operation: 'zohobooks.purchaseorder.list';
1681
1740
  } ? {
@@ -1685,7 +1744,7 @@ export type IntegrationResult<R extends IntegrationRequest> = R extends {
1685
1744
  } : R extends {
1686
1745
  operation: 'zohobooks.invoice.pdf';
1687
1746
  } ? GmailAttachmentReadResult : R extends {
1688
- operation: 'zohobooks.bill.create' | 'zohobooks.invoice.create';
1747
+ operation: 'zohobooks.bill.create' | 'zohobooks.invoice.create' | 'zohobooks.invoice.reviseDraft' | 'zohobooks.creditnote.create';
1689
1748
  } ? {
1690
1749
  id: string;
1691
1750
  } : R extends ZohoBooksRequest ? {
@@ -1895,6 +1954,7 @@ export declare function createClient(options?: ClientOptions): {
1895
1954
  data: {
1896
1955
  from: <T = Record<string, unknown>>(table: string) => QueryBuilder<T, T[]>;
1897
1956
  rpc: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
1957
+ watch: <T = Record<string, unknown>>(build: () => QueryBuilder<T, any>, onResult: (result: WatchResult) => void, options?: WatchOptions) => (() => void);
1898
1958
  };
1899
1959
  files: {
1900
1960
  createUpload: (input: {
@@ -1925,6 +1985,11 @@ export declare function createClient(options?: ClientOptions): {
1925
1985
  }) => Promise<T>;
1926
1986
  };
1927
1987
  jobs: {
1988
+ /** Select the owner's folder for a declared configurable file trigger. */
1989
+ configureTrigger: (name: string, configuration: {
1990
+ folderId: string;
1991
+ }) => Promise<JobTriggerStatus>;
1992
+ getTrigger: (name: string) => Promise<JobTriggerStatus>;
1928
1993
  enqueue: (name: string, input?: unknown) => Promise<{
1929
1994
  id: string;
1930
1995
  }>;
@@ -1962,6 +2027,13 @@ export type HarbourResult<T> = IsomorphResult<T>;
1962
2027
  export type HarbourClient = IsomorphClient;
1963
2028
  /** @deprecated Internal transformation lane only; a kit app uses IsomorphCompatSource. */
1964
2029
  export type HarbourCompatSource = import('./supabase-compat.js').IsomorphCompatSource;
2030
+ /** Owner-selected folder; activation still requires the owner's background consent. */
2031
+ export interface JobTriggerStatus {
2032
+ name: string;
2033
+ folderId?: string;
2034
+ state: 'configured' | 'unconfigured';
2035
+ updatedAt?: string;
2036
+ }
1965
2037
  /** Provider file metadata delivered to a file.created job; bytes are read through its connection. */
1966
2038
  export interface FileArrivalInput {
1967
2039
  type: 'file.created';
package/dist/index.js CHANGED
@@ -707,6 +707,45 @@ export function createClient(options = {}) {
707
707
  data: {
708
708
  from: (table) => new QueryBuilder(transport, table),
709
709
  rpc: (name, args = {}) => transport.json(`/_harbour/data/rpc/${encodeURIComponent(name)}`, { method: 'POST', body: JSON.stringify(args) }),
710
+ // A query on a timer: QueryBuilder.execute() now and every `every` ms,
711
+ // no read while the tab is hidden, the interval doubling up to
712
+ // `maxEvery` while the answer is unchanged. No new transport or route;
713
+ // each read is an ordinary read (it counts as app activity), so watch
714
+ // the job row, not the data table. A failed read is delivered as
715
+ // { data: null, error } and the timer keeps going.
716
+ watch: (build, onResult, options = {}) => {
717
+ const every = options.every ?? 3_000;
718
+ const maxEvery = options.maxEvery ?? 15_000;
719
+ let delay = every;
720
+ let previous;
721
+ let stopped = false;
722
+ let timer;
723
+ const schedule = () => { if (!stopped)
724
+ timer = setTimer(tick, delay); };
725
+ const tick = async () => {
726
+ if (typeof document !== 'undefined' && document.hidden) {
727
+ schedule();
728
+ return;
729
+ }
730
+ let result;
731
+ try {
732
+ result = await build().execute();
733
+ }
734
+ catch (error) {
735
+ result = { data: null, error: error instanceof Error ? error : new Error(String(error)) };
736
+ }
737
+ if (stopped)
738
+ return;
739
+ const json = JSON.stringify({ ...result, error: result.error?.message });
740
+ delay = json === previous ? Math.min(delay * 2, maxEvery) : every;
741
+ previous = json;
742
+ onResult(result);
743
+ schedule();
744
+ };
745
+ void tick();
746
+ return () => { stopped = true; if (timer !== undefined)
747
+ clearTimer(timer); };
748
+ },
710
749
  },
711
750
  files: {
712
751
  createUpload: (input) => transport.json('/_harbour/files/uploads', { method: 'POST', body: JSON.stringify(input) }),
@@ -746,6 +785,9 @@ export function createClient(options = {}) {
746
785
  },
747
786
  },
748
787
  jobs: {
788
+ /** Select the owner's folder for a declared configurable file trigger. */
789
+ configureTrigger: (name, configuration) => transport.json('/_harbour/integrations/trigger-config', { method: 'POST', body: JSON.stringify({ name, configuration }) }),
790
+ getTrigger: (name) => transport.json('/_harbour/integrations/trigger-config', { method: 'POST', body: JSON.stringify({ name }) }),
749
791
  enqueue: (name, input) => transport.json('/_harbour/actions/isomorph-jobs-enqueue', { method: 'POST', body: JSON.stringify({ name, input: input ?? null }) }),
750
792
  get: (id) => transport.json('/_harbour/actions/isomorph-jobs-get', { method: 'POST', body: JSON.stringify({ id }) }),
751
793
  cancel: (id) => transport.json('/_harbour/actions/isomorph-jobs-cancel', { method: 'POST', body: JSON.stringify({ id }) }),
@@ -2386,6 +2386,13 @@ export declare const IntegrationExecuteInputRules: {
2386
2386
  readonly minLength: 0;
2387
2387
  readonly maxLength: 2000;
2388
2388
  };
2389
+ readonly salesOrderItemId: {
2390
+ readonly kind: "string";
2391
+ readonly required: false;
2392
+ readonly minLength: 0;
2393
+ readonly maxLength: 32;
2394
+ readonly pattern: "^[0-9]{1,32}$";
2395
+ };
2389
2396
  };
2390
2397
  };
2391
2398
  };
@@ -2414,6 +2421,129 @@ export declare const IntegrationExecuteInputRules: {
2414
2421
  };
2415
2422
  };
2416
2423
  };
2424
+ readonly "zohobooks.salesorder.list": {
2425
+ readonly idempotencyKey: false;
2426
+ readonly fields: {
2427
+ readonly customerId: {
2428
+ readonly kind: "string";
2429
+ readonly required: true;
2430
+ readonly minLength: 0;
2431
+ readonly maxLength: 32;
2432
+ readonly pattern: "^[0-9]{1,32}$";
2433
+ };
2434
+ readonly pageToken: {
2435
+ readonly kind: "string";
2436
+ readonly required: false;
2437
+ readonly minLength: 1;
2438
+ readonly maxLength: 2048;
2439
+ };
2440
+ };
2441
+ };
2442
+ readonly "zohobooks.salesorder.read": {
2443
+ readonly idempotencyKey: false;
2444
+ readonly fields: {
2445
+ readonly documentId: {
2446
+ readonly kind: "string";
2447
+ readonly required: true;
2448
+ readonly minLength: 0;
2449
+ readonly maxLength: 32;
2450
+ readonly pattern: "^[0-9]{1,32}$";
2451
+ };
2452
+ };
2453
+ };
2454
+ readonly "zohobooks.invoice.reviseDraft": {
2455
+ readonly idempotencyKey: true;
2456
+ readonly fields: {
2457
+ readonly documentId: {
2458
+ readonly kind: "string";
2459
+ readonly required: true;
2460
+ readonly minLength: 0;
2461
+ readonly maxLength: 32;
2462
+ readonly pattern: "^[0-9]{1,32}$";
2463
+ };
2464
+ readonly lineItems: {
2465
+ readonly kind: "object[]";
2466
+ readonly required: true;
2467
+ readonly minItems: 1;
2468
+ readonly maxItems: 100;
2469
+ readonly fields: {
2470
+ readonly lineItemId: {
2471
+ readonly kind: "string";
2472
+ readonly required: true;
2473
+ readonly minLength: 0;
2474
+ readonly maxLength: 32;
2475
+ readonly pattern: "^[0-9]{1,32}$";
2476
+ };
2477
+ readonly quantity: {
2478
+ readonly kind: "string";
2479
+ readonly required: true;
2480
+ readonly minLength: 0;
2481
+ readonly maxLength: 17;
2482
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2483
+ };
2484
+ };
2485
+ };
2486
+ };
2487
+ };
2488
+ readonly "zohobooks.creditnote.create": {
2489
+ readonly idempotencyKey: true;
2490
+ readonly fields: {
2491
+ readonly documentId: {
2492
+ readonly kind: "string";
2493
+ readonly required: true;
2494
+ readonly minLength: 0;
2495
+ readonly maxLength: 32;
2496
+ readonly pattern: "^[0-9]{1,32}$";
2497
+ };
2498
+ readonly documentDate: {
2499
+ readonly kind: "string";
2500
+ readonly required: true;
2501
+ readonly minLength: 0;
2502
+ readonly maxLength: 10;
2503
+ readonly pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$";
2504
+ };
2505
+ readonly referenceNumber: {
2506
+ readonly kind: "string";
2507
+ readonly required: false;
2508
+ readonly minLength: 1;
2509
+ readonly maxLength: 100;
2510
+ };
2511
+ readonly lineItems: {
2512
+ readonly kind: "object[]";
2513
+ readonly required: true;
2514
+ readonly minItems: 1;
2515
+ readonly maxItems: 100;
2516
+ readonly fields: {
2517
+ readonly lineItemId: {
2518
+ readonly kind: "string";
2519
+ readonly required: true;
2520
+ readonly minLength: 0;
2521
+ readonly maxLength: 32;
2522
+ readonly pattern: "^[0-9]{1,32}$";
2523
+ };
2524
+ readonly quantity: {
2525
+ readonly kind: "string";
2526
+ readonly required: true;
2527
+ readonly minLength: 0;
2528
+ readonly maxLength: 17;
2529
+ readonly pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$";
2530
+ };
2531
+ };
2532
+ };
2533
+ };
2534
+ };
2535
+ readonly "zohobooks.creditnote.read": {
2536
+ readonly idempotencyKey: false;
2537
+ readonly fields: {
2538
+ readonly documentId: {
2539
+ readonly kind: "string";
2540
+ readonly required: true;
2541
+ readonly minLength: 0;
2542
+ readonly maxLength: 32;
2543
+ readonly pattern: "^[0-9]{1,32}$";
2544
+ };
2545
+ };
2546
+ };
2417
2547
  };
2418
2548
  export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
2419
2549
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
@@ -729,6 +729,7 @@ export const IntegrationExecuteInputRules = {
729
729
  rate: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
730
730
  taxId: { kind: "string", required: false, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
731
731
  description: { kind: "string", required: false, minLength: 0, maxLength: 2000 },
732
+ salesOrderItemId: { kind: "string", required: false, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
732
733
  } },
733
734
  },
734
735
  },
@@ -744,6 +745,47 @@ export const IntegrationExecuteInputRules = {
744
745
  documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
745
746
  },
746
747
  },
748
+ "zohobooks.salesorder.list": {
749
+ idempotencyKey: false,
750
+ fields: {
751
+ customerId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
752
+ pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
753
+ },
754
+ },
755
+ "zohobooks.salesorder.read": {
756
+ idempotencyKey: false,
757
+ fields: {
758
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
759
+ },
760
+ },
761
+ "zohobooks.invoice.reviseDraft": {
762
+ idempotencyKey: true,
763
+ fields: {
764
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
765
+ lineItems: { kind: "object[]", required: true, minItems: 1, maxItems: 100, fields: {
766
+ lineItemId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
767
+ quantity: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
768
+ } },
769
+ },
770
+ },
771
+ "zohobooks.creditnote.create": {
772
+ idempotencyKey: true,
773
+ fields: {
774
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
775
+ documentDate: { kind: "string", required: true, minLength: 0, maxLength: 10, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
776
+ referenceNumber: { kind: "string", required: false, minLength: 1, maxLength: 100 },
777
+ lineItems: { kind: "object[]", required: true, minItems: 1, maxItems: 100, fields: {
778
+ lineItemId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
779
+ quantity: { kind: "string", required: true, minLength: 0, maxLength: 17, pattern: "^[0-9]{1,10}(\\.[0-9]{1,6})?$" },
780
+ } },
781
+ },
782
+ },
783
+ "zohobooks.creditnote.read": {
784
+ idempotencyKey: false,
785
+ fields: {
786
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
787
+ },
788
+ },
747
789
  };
748
790
  /** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
749
791
  export function integrationExecuteInputMaximum(operation, field) {
@@ -623,4 +623,39 @@ export declare const IntegrationOperations: {
623
623
  readonly declaredMode: false;
624
624
  readonly idempotencyKey: false;
625
625
  };
626
+ readonly "zohobooks.salesorder.list": {
627
+ readonly kind: "saas";
628
+ readonly identities: readonly ["user"];
629
+ readonly readOnly: true;
630
+ readonly declaredMode: false;
631
+ readonly idempotencyKey: false;
632
+ };
633
+ readonly "zohobooks.salesorder.read": {
634
+ readonly kind: "saas";
635
+ readonly identities: readonly ["user"];
636
+ readonly readOnly: true;
637
+ readonly declaredMode: false;
638
+ readonly idempotencyKey: false;
639
+ };
640
+ readonly "zohobooks.invoice.reviseDraft": {
641
+ readonly kind: "saas";
642
+ readonly identities: readonly ["user"];
643
+ readonly readOnly: false;
644
+ readonly declaredMode: false;
645
+ readonly idempotencyKey: true;
646
+ };
647
+ readonly "zohobooks.creditnote.create": {
648
+ readonly kind: "saas";
649
+ readonly identities: readonly ["user"];
650
+ readonly readOnly: false;
651
+ readonly declaredMode: false;
652
+ readonly idempotencyKey: true;
653
+ };
654
+ readonly "zohobooks.creditnote.read": {
655
+ readonly kind: "saas";
656
+ readonly identities: readonly ["user"];
657
+ readonly readOnly: true;
658
+ readonly declaredMode: false;
659
+ readonly idempotencyKey: false;
660
+ };
626
661
  };
@@ -806,5 +806,50 @@ export const IntegrationOperations = {
806
806
  "readOnly": true,
807
807
  "declaredMode": false,
808
808
  "idempotencyKey": false
809
+ },
810
+ "zohobooks.salesorder.list": {
811
+ "kind": "saas",
812
+ "identities": [
813
+ "user"
814
+ ],
815
+ "readOnly": true,
816
+ "declaredMode": false,
817
+ "idempotencyKey": false
818
+ },
819
+ "zohobooks.salesorder.read": {
820
+ "kind": "saas",
821
+ "identities": [
822
+ "user"
823
+ ],
824
+ "readOnly": true,
825
+ "declaredMode": false,
826
+ "idempotencyKey": false
827
+ },
828
+ "zohobooks.invoice.reviseDraft": {
829
+ "kind": "saas",
830
+ "identities": [
831
+ "user"
832
+ ],
833
+ "readOnly": false,
834
+ "declaredMode": false,
835
+ "idempotencyKey": true
836
+ },
837
+ "zohobooks.creditnote.create": {
838
+ "kind": "saas",
839
+ "identities": [
840
+ "user"
841
+ ],
842
+ "readOnly": false,
843
+ "declaredMode": false,
844
+ "idempotencyKey": true
845
+ },
846
+ "zohobooks.creditnote.read": {
847
+ "kind": "saas",
848
+ "identities": [
849
+ "user"
850
+ ],
851
+ "readOnly": true,
852
+ "declaredMode": false,
853
+ "idempotencyKey": false
809
854
  }
810
855
  };
package/dist/jobs.d.ts CHANGED
@@ -8,13 +8,14 @@ export interface EmailArrivalTrigger {
8
8
  subjectContains?: string;
9
9
  };
10
10
  }
11
- export declare const FILE_MIME_TYPES: readonly ["application/pdf", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"];
11
+ export declare const FILE_MIME_TYPES: readonly ["application/pdf", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "image/png", "image/jpeg"];
12
12
  export declare const providerFileId: (value: unknown) => value is string;
13
13
  export interface FileCreatedTrigger {
14
14
  type: 'file.created';
15
15
  connection: string;
16
16
  resource: string;
17
- folderId: string;
17
+ folderId?: string;
18
+ configurableFolder?: true;
18
19
  driveId?: string;
19
20
  mimeTypes?: string[];
20
21
  }
package/dist/jobs.js CHANGED
@@ -6,7 +6,7 @@ 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
- export const FILE_MIME_TYPES = ['application/pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
9
+ export const FILE_MIME_TYPES = ['application/pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'image/png', 'image/jpeg'];
10
10
  export const providerFileId = (value) => typeof value === 'string' && /^[A-Za-z0-9!._,=-]{1,256}$/.test(value);
11
11
  function literalObject(node) {
12
12
  const value = node;
@@ -30,12 +30,13 @@ function literalObject(node) {
30
30
  export function eventTrigger(value) {
31
31
  if (value?.type === 'file.created') {
32
32
  const t = value;
33
- if (Object.keys(t).some(k => !['type', 'connection', 'resource', 'folderId', 'driveId', 'mimeTypes'].includes(k))
33
+ if (Object.keys(t).some(k => !['type', 'connection', 'resource', 'folderId', 'configurableFolder', 'driveId', 'mimeTypes'].includes(k))
34
34
  || typeof t.connection !== 'string' || !LOGICAL_NAME.test(t.connection)
35
35
  || typeof t.resource !== 'string' || !t.resource.trim() || t.resource.length > 320
36
- || !providerFileId(t.folderId) || (t.driveId !== undefined && !providerFileId(t.driveId))
37
- || (t.mimeTypes !== undefined && (!Array.isArray(t.mimeTypes) || !t.mimeTypes.length || t.mimeTypes.length > 2 || new Set(t.mimeTypes).size !== t.mimeTypes.length || t.mimeTypes.some(m => !FILE_MIME_TYPES.includes(m)))))
38
- throw new Error('file.created requires a connection, resource, folder ID and optional PDF/XLSX MIME types');
36
+ || (t.configurableFolder !== undefined && t.configurableFolder !== true)
37
+ || (t.folderId === undefined ? t.configurableFolder !== true : !providerFileId(t.folderId)) || (t.driveId !== undefined && !providerFileId(t.driveId))
38
+ || (t.mimeTypes !== undefined && (!Array.isArray(t.mimeTypes) || !t.mimeTypes.length || t.mimeTypes.length > FILE_MIME_TYPES.length || new Set(t.mimeTypes).size !== t.mimeTypes.length || t.mimeTypes.some(m => !FILE_MIME_TYPES.includes(m)))))
39
+ throw new Error('file.created requires a connection, resource, folder ID and optional PDF/XLSX/PNG/JPEG MIME types');
39
40
  return t;
40
41
  }
41
42
  const trigger = value;
package/dist/runtime.js CHANGED
@@ -395,7 +395,7 @@ export class Runtime {
395
395
  if (job.trigger.type === 'file.created') {
396
396
  const instant = (v) => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(v) && Number.isFinite(Date.parse(v));
397
397
  if (Object.keys(input).some(k => !['type', 'provider', 'sourceId', 'fileId', 'folderId', 'driveId', 'name', 'mimeType', 'size', 'createdAt', 'observedAt'].includes(k))
398
- || input.type !== 'file.created' || !['drive', 'sharepoint'].includes(String(input.provider)) || !providerFileId(input.fileId) || input.folderId !== job.trigger.folderId
398
+ || input.type !== 'file.created' || !['drive', 'sharepoint'].includes(String(input.provider)) || !providerFileId(input.fileId) || !providerFileId(input.folderId) || (!job.trigger.configurableFolder && input.folderId !== job.trigger.folderId)
399
399
  || (input.driveId !== undefined && !providerFileId(input.driveId)) || (input.provider === 'sharepoint' && !input.driveId)
400
400
  || (job.trigger.driveId !== undefined && input.driveId !== job.trigger.driveId)
401
401
  || typeof input.name !== 'string' || !input.name || input.name.length > 1024 || !Number.isSafeInteger(input.size) || Number(input.size) < 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.32.0",
3
+ "version": "1.34.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",