@isomorph.ai/app-sdk 1.36.1 → 1.36.3

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
@@ -1177,6 +1177,13 @@ export type ZohoBooksRequest = {
1177
1177
  quantity: string;
1178
1178
  }[];
1179
1179
  };
1180
+ } | {
1181
+ operation: 'zohobooks.invoice.markSent';
1182
+ resource: 'organization';
1183
+ idempotencyKey: string;
1184
+ input: {
1185
+ documentId: string;
1186
+ };
1180
1187
  } | {
1181
1188
  operation: 'zohobooks.creditnote.create';
1182
1189
  resource: 'organization';
@@ -1744,7 +1751,7 @@ export type IntegrationResult<R extends IntegrationRequest> = R extends {
1744
1751
  } : R extends {
1745
1752
  operation: 'zohobooks.invoice.pdf';
1746
1753
  } ? GmailAttachmentReadResult : R extends {
1747
- operation: 'zohobooks.bill.create' | 'zohobooks.invoice.create' | 'zohobooks.invoice.reviseDraft' | 'zohobooks.creditnote.create';
1754
+ operation: 'zohobooks.bill.create' | 'zohobooks.invoice.create' | 'zohobooks.invoice.reviseDraft' | 'zohobooks.invoice.markSent' | 'zohobooks.creditnote.create';
1748
1755
  } ? {
1749
1756
  id: string;
1750
1757
  } : R extends ZohoBooksRequest ? {
@@ -2485,6 +2485,18 @@ export declare const IntegrationExecuteInputRules: {
2485
2485
  };
2486
2486
  };
2487
2487
  };
2488
+ readonly "zohobooks.invoice.markSent": {
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
+ };
2499
+ };
2488
2500
  readonly "zohobooks.creditnote.create": {
2489
2501
  readonly idempotencyKey: true;
2490
2502
  readonly fields: {
@@ -768,6 +768,12 @@ export const IntegrationExecuteInputRules = {
768
768
  } },
769
769
  },
770
770
  },
771
+ "zohobooks.invoice.markSent": {
772
+ idempotencyKey: true,
773
+ fields: {
774
+ documentId: { kind: "string", required: true, minLength: 0, maxLength: 32, pattern: "^[0-9]{1,32}$" },
775
+ },
776
+ },
771
777
  "zohobooks.creditnote.create": {
772
778
  idempotencyKey: true,
773
779
  fields: {
@@ -644,6 +644,13 @@ export declare const IntegrationOperations: {
644
644
  readonly declaredMode: false;
645
645
  readonly idempotencyKey: true;
646
646
  };
647
+ readonly "zohobooks.invoice.markSent": {
648
+ readonly kind: "saas";
649
+ readonly identities: readonly ["user"];
650
+ readonly readOnly: false;
651
+ readonly declaredMode: false;
652
+ readonly idempotencyKey: true;
653
+ };
647
654
  readonly "zohobooks.creditnote.create": {
648
655
  readonly kind: "saas";
649
656
  readonly identities: readonly ["user"];
@@ -834,6 +834,15 @@ export const IntegrationOperations = {
834
834
  "declaredMode": false,
835
835
  "idempotencyKey": true
836
836
  },
837
+ "zohobooks.invoice.markSent": {
838
+ "kind": "saas",
839
+ "identities": [
840
+ "user"
841
+ ],
842
+ "readOnly": false,
843
+ "declaredMode": false,
844
+ "idempotencyKey": true
845
+ },
837
846
  "zohobooks.creditnote.create": {
838
847
  "kind": "saas",
839
848
  "identities": [
package/dist/runtime.js CHANGED
@@ -237,18 +237,31 @@ export class Runs {
237
237
  const interrupted = expired.filter(run => !active.has(run.id));
238
238
  for (const run of interrupted) {
239
239
  const exhausted = run.attempt >= run.max_attempts;
240
- await this.table().update({ status: exhausted ? 'failed' : 'queued', error: exhausted ? 'the worker was interrupted and no attempts remain' : run.error, lease_until: null, updated_at: now.toISOString() }).eq('id', run.id).eq('status', 'running');
240
+ const status = run.cancel_requested ? 'cancelled' : exhausted ? 'failed' : 'queued';
241
+ const { data } = await this.table().update({ status, error: status === 'failed' ? 'the worker was interrupted and no attempts remain' : run.error, lease_until: null, updated_at: now.toISOString() }).eq('id', run.id).eq('status', 'running').eq('cancel_requested', run.cancel_requested).select('id').maybeSingle();
242
+ // Cancellation may have arrived between reading the expired lease and
243
+ // writing its retry. It wins that race without leaving another attempt.
244
+ if (!data && !run.cancel_requested)
245
+ await this.table().update({ status: 'cancelled', lease_until: null, updated_at: now.toISOString() }).eq('id', run.id).eq('status', 'running').eq('cancel_requested', true);
241
246
  }
242
247
  return interrupted.length;
243
248
  }
244
249
  /** Claims the oldest queued run by a conditional update; null when the queue is empty or another worker won. */
245
250
  async claim(now) {
246
- const { data: queued } = await this.table().select('id, attempt').eq('status', 'queued').order('created_at').limit(1);
247
- const next = queued[0];
248
- if (!next)
249
- return null;
250
- const { data } = await this.table().update({ status: 'running', attempt: next.attempt + 1, lease_until: new Date(now.getTime() + LEASE_MS).toISOString(), updated_at: now.toISOString() }).eq('id', next.id).eq('status', 'queued').select('*').maybeSingle();
251
- return data;
251
+ while (true) {
252
+ const { data: queued } = await this.table().select('id, attempt, cancel_requested').eq('status', 'queued').order('created_at').limit(1);
253
+ const next = queued[0];
254
+ if (!next)
255
+ return null;
256
+ if (next.cancel_requested) {
257
+ const { data } = await this.table().update({ status: 'cancelled', lease_until: null, updated_at: now.toISOString() }).eq('id', next.id).eq('status', 'queued').eq('cancel_requested', true).select('id').maybeSingle();
258
+ if (!data)
259
+ return null;
260
+ continue;
261
+ }
262
+ const { data } = await this.table().update({ status: 'running', attempt: next.attempt + 1, lease_until: new Date(now.getTime() + LEASE_MS).toISOString(), updated_at: now.toISOString() }).eq('id', next.id).eq('status', 'queued').eq('cancel_requested', false).select('*').maybeSingle();
263
+ return data;
264
+ }
252
265
  }
253
266
  async progress(id, percent, note) {
254
267
  await this.table().update({ progress: Math.max(0, Math.min(100, Math.round(percent))), ...(note === undefined ? {} : { note }), lease_until: new Date(Date.now() + LEASE_MS).toISOString(), updated_at: new Date().toISOString() }).eq('id', id);
@@ -261,8 +274,16 @@ export class Runs {
261
274
  async finish(run, outcome) {
262
275
  const retry = outcome.error !== undefined && !outcome.cancelled && run.attempt < run.max_attempts;
263
276
  const status = outcome.cancelled ? 'cancelled' : outcome.error === undefined ? 'succeeded' : retry ? 'queued' : 'failed';
264
- await this.table().update({ status, result: outcome.result ?? null, error: outcome.error ?? null, progress: status === 'succeeded' ? 100 : run.progress, lease_until: null, updated_at: new Date().toISOString() }).eq('id', run.id).eq('status', 'running');
265
- return status;
277
+ const now = new Date().toISOString();
278
+ if (!outcome.cancelled) {
279
+ const { data } = await this.table().update({ status, result: outcome.result ?? null, error: outcome.error ?? null, progress: status === 'succeeded' ? 100 : run.progress, lease_until: null, updated_at: now }).eq('id', run.id).eq('status', 'running').eq('cancel_requested', false).select('id').maybeSingle();
280
+ if (data)
281
+ return status;
282
+ }
283
+ // The handler may throw after the user cancels. A cancelled run is
284
+ // terminal even when that failure would otherwise be retried.
285
+ await this.table().update({ status: 'cancelled', result: null, error: null, lease_until: null, updated_at: now }).eq('id', run.id).eq('status', 'running').eq('cancel_requested', true);
286
+ return 'cancelled';
266
287
  }
267
288
  }
268
289
  export class Runtime {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.36.1",
3
+ "version": "1.36.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",