@isomorph.ai/app-sdk 1.32.0 → 1.33.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 +26 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +39 -0
- package/package.json +1 -1
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
|
/**
|
|
@@ -1895,6 +1904,7 @@ export declare function createClient(options?: ClientOptions): {
|
|
|
1895
1904
|
data: {
|
|
1896
1905
|
from: <T = Record<string, unknown>>(table: string) => QueryBuilder<T, T[]>;
|
|
1897
1906
|
rpc: <T = unknown>(name: string, args?: Record<string, unknown>) => Promise<T>;
|
|
1907
|
+
watch: <T = Record<string, unknown>>(build: () => QueryBuilder<T, any>, onResult: (result: WatchResult) => void, options?: WatchOptions) => (() => void);
|
|
1898
1908
|
};
|
|
1899
1909
|
files: {
|
|
1900
1910
|
createUpload: (input: {
|
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) }),
|