@driggsby/sdk 0.4.0 → 0.5.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 +16 -0
- package/dist/driggsby-sdk.js +11 -4
- package/dist/index.d.ts +1 -1
- package/dist/sdk-core.d.ts +8 -1
- package/dist/sdk-core.js +18 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,22 @@ the callback runs again with fresh data. It returns an unsubscribe
|
|
|
22
22
|
function. Identical results are deduped, so unchanged data never
|
|
23
23
|
re-renders.
|
|
24
24
|
|
|
25
|
+
To show a section's own failure, pass `onError`. It runs when a watch
|
|
26
|
+
fails, after any retries the host makes, with `{ message, kind }`. A
|
|
27
|
+
repeat of the same failure is skipped. `kind` is `"timeout"`,
|
|
28
|
+
`"unavailable"`, `"busy"`, `"rate_limited"` or `null`. Under
|
|
29
|
+
`driggsby dev`, which doesn't retry, `kind` is always `null`. The next
|
|
30
|
+
successful result always reaches your callback, even when it matches
|
|
31
|
+
the data from before the failure, so clear the failure there. Show
|
|
32
|
+
`error.message` as text (for example with `textContent`), never as
|
|
33
|
+
HTML:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
driggsby.watch("list_accounts", {}, render, {
|
|
37
|
+
onError: (error) => showFailure(error.message),
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
25
41
|
This package exists for two narrower uses:
|
|
26
42
|
|
|
27
43
|
- `driggsby dev` serves the same runtime locally from this package's
|
package/dist/driggsby-sdk.js
CHANGED
|
@@ -11,6 +11,7 @@ function isAllowedHostOrigin(origin, allowLocalHostOrigins) {
|
|
|
11
11
|
if (EXACT_ALLOWED_HOST_ORIGINS.has(origin)) return true;
|
|
12
12
|
return allowLocalHostOrigins && LOCAL_HOST_ORIGIN_PATTERN.test(origin);
|
|
13
13
|
}
|
|
14
|
+
var WATCH_ERROR_KINDS = /* @__PURE__ */ new Set(["timeout", "unavailable", "busy", "rate_limited"]);
|
|
14
15
|
var APP_ROUTE_PATTERN = /^#[A-Za-z0-9/\-._]{1,256}$/;
|
|
15
16
|
function sanitizeAppRoute(value) {
|
|
16
17
|
return typeof value === "string" && APP_ROUTE_PATTERN.test(value) ? value : "";
|
|
@@ -22,16 +23,20 @@ function parseProtocolMessage(data) {
|
|
|
22
23
|
const type = record.type;
|
|
23
24
|
if (typeof type !== "string") return null;
|
|
24
25
|
let errorMessage = null;
|
|
26
|
+
let errorKind = null;
|
|
25
27
|
const error = record.error;
|
|
26
28
|
if (typeof error === "object" && error !== null) {
|
|
27
29
|
const message = error.message;
|
|
28
30
|
errorMessage = typeof message === "string" ? message : "Something went wrong running this tool.";
|
|
31
|
+
const kind = error.kind;
|
|
32
|
+
errorKind = typeof kind === "string" && WATCH_ERROR_KINDS.has(kind) ? kind : null;
|
|
29
33
|
}
|
|
30
34
|
return {
|
|
31
35
|
type,
|
|
32
36
|
id: typeof record.id === "string" ? record.id : null,
|
|
33
37
|
result: record.result,
|
|
34
38
|
errorMessage,
|
|
39
|
+
errorKind,
|
|
35
40
|
route: sanitizeAppRoute(record.route)
|
|
36
41
|
};
|
|
37
42
|
}
|
|
@@ -57,14 +62,15 @@ var SdkCore = class {
|
|
|
57
62
|
// params is typed loose on purpose: app authors call this from untyped
|
|
58
63
|
// JavaScript, and a null/omitted params should mean "no params", not a
|
|
59
64
|
// crash.
|
|
60
|
-
watch(tool, params, callback) {
|
|
65
|
+
watch(tool, params, callback, options) {
|
|
61
66
|
if (this.watches.size >= MAX_WATCHES) {
|
|
62
67
|
this.onToolError?.(tool, `Watch limit reached (${String(MAX_WATCHES)}); this watch will not receive data. Unsubscribe watches you no longer need.`);
|
|
63
68
|
return () => void 0;
|
|
64
69
|
}
|
|
65
70
|
this.sequence += 1;
|
|
66
71
|
const id = `w${this.sequence}`;
|
|
67
|
-
|
|
72
|
+
const onError = typeof options?.onError === "function" ? options.onError : null;
|
|
73
|
+
this.watches.set(id, { tool, params: params ?? {}, callback, onError, lastDeliveredJson: null });
|
|
68
74
|
this.requestWatch(id);
|
|
69
75
|
return () => {
|
|
70
76
|
this.watches.delete(id);
|
|
@@ -116,10 +122,11 @@ var SdkCore = class {
|
|
|
116
122
|
const entry = message.id === null ? void 0 : this.watches.get(message.id);
|
|
117
123
|
if (entry === void 0) return;
|
|
118
124
|
if (message.errorMessage !== null) {
|
|
119
|
-
const json2 = JSON.stringify({ error: message.errorMessage });
|
|
125
|
+
const json2 = JSON.stringify({ error: message.errorMessage, kind: message.errorKind });
|
|
120
126
|
if (json2 === entry.lastDeliveredJson) return;
|
|
121
127
|
entry.lastDeliveredJson = json2;
|
|
122
128
|
this.onToolError?.(entry.tool, message.errorMessage);
|
|
129
|
+
entry.onError?.({ message: message.errorMessage, kind: message.errorKind });
|
|
123
130
|
return;
|
|
124
131
|
}
|
|
125
132
|
const json = JSON.stringify({ result: message.result });
|
|
@@ -191,7 +198,7 @@ window.addEventListener("message", (event) => {
|
|
|
191
198
|
core.handleMessage(event.origin, event.source === window.parent, event.data);
|
|
192
199
|
});
|
|
193
200
|
window.driggsby = {
|
|
194
|
-
watch: (tool, params, callback) => core.watch(tool, params, callback)
|
|
201
|
+
watch: (tool, params, callback, options) => core.watch(tool, params, callback, options)
|
|
195
202
|
};
|
|
196
203
|
if (window.parent === window) {
|
|
197
204
|
whenBodyReady(showStandaloneNote);
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MAX_WATCHES, PROTOCOL, SdkCore, isAllowedHostOrigin, isLocalAppHostname, type PostFunction, type WatchCallback, } from "./sdk-core.ts";
|
|
1
|
+
export { MAX_WATCHES, PROTOCOL, SdkCore, isAllowedHostOrigin, isLocalAppHostname, type PostFunction, type WatchCallback, type WatchError, type WatchOptions, } from "./sdk-core.ts";
|
package/dist/sdk-core.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export declare const MAX_WATCHES = 32;
|
|
|
3
3
|
export declare function isLocalAppHostname(hostname: string): boolean;
|
|
4
4
|
export declare function isAllowedHostOrigin(origin: string, allowLocalHostOrigins: boolean): boolean;
|
|
5
5
|
export type WatchCallback = (result: unknown) => void;
|
|
6
|
+
export interface WatchError {
|
|
7
|
+
message: string;
|
|
8
|
+
kind: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface WatchOptions {
|
|
11
|
+
onError?: (error: WatchError) => void;
|
|
12
|
+
}
|
|
6
13
|
export type PostFunction = (message: Record<string, unknown>, targetOrigin: string) => void;
|
|
7
14
|
export declare function sanitizeAppRoute(value: unknown): string;
|
|
8
15
|
export declare class SdkCore {
|
|
@@ -19,7 +26,7 @@ export declare class SdkCore {
|
|
|
19
26
|
allowLocalHostOrigins?: boolean;
|
|
20
27
|
});
|
|
21
28
|
get hostReady(): boolean;
|
|
22
|
-
watch(tool: string, params: Record<string, unknown> | null | undefined, callback: WatchCallback): () => void;
|
|
29
|
+
watch(tool: string, params: Record<string, unknown> | null | undefined, callback: WatchCallback, options?: WatchOptions | null): () => void;
|
|
23
30
|
handleMessage(origin: string, fromParent: boolean, data: unknown): void;
|
|
24
31
|
private handleHello;
|
|
25
32
|
reportRoute(hash: string): void;
|
package/dist/sdk-core.js
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
// first-valid-hello pin, and the parent-source requirement.
|
|
7
7
|
//
|
|
8
8
|
// The whole public surface an app sees is:
|
|
9
|
-
// driggsby.watch(tool, params, callback) -> unsubscribe()
|
|
9
|
+
// driggsby.watch(tool, params, callback[, { onError }]) -> unsubscribe()
|
|
10
|
+
// onError(error) receives { message, kind } when a watch's call fails
|
|
11
|
+
// after the host's retries; kind is "timeout", "unavailable", "busy",
|
|
12
|
+
// "rate_limited" or null.
|
|
10
13
|
// Every read is a live subscription: the host announces "data-changed" and
|
|
11
14
|
// each watch re-runs; results are deduped by JSON identity so unchanged
|
|
12
15
|
// data never re-renders.
|
|
@@ -38,6 +41,9 @@ export function isAllowedHostOrigin(origin, allowLocalHostOrigins) {
|
|
|
38
41
|
return true;
|
|
39
42
|
return allowLocalHostOrigins && LOCAL_HOST_ORIGIN_PATTERN.test(origin);
|
|
40
43
|
}
|
|
44
|
+
// The failure kinds a watch may be told about; the host forwards only
|
|
45
|
+
// these, and anything else reads as null.
|
|
46
|
+
const WATCH_ERROR_KINDS = new Set(["timeout", "unavailable", "busy", "rate_limited"]);
|
|
41
47
|
// The app's in-page location, as a URL fragment. The host mirrors it into
|
|
42
48
|
// its own page URL (so refresh and shared links restore it) and hands it
|
|
43
49
|
// back in the hello after a reload. The shape is deliberately narrow — a
|
|
@@ -58,16 +64,20 @@ function parseProtocolMessage(data) {
|
|
|
58
64
|
if (typeof type !== "string")
|
|
59
65
|
return null;
|
|
60
66
|
let errorMessage = null;
|
|
67
|
+
let errorKind = null;
|
|
61
68
|
const error = record.error;
|
|
62
69
|
if (typeof error === "object" && error !== null) {
|
|
63
70
|
const message = error.message;
|
|
64
71
|
errorMessage = typeof message === "string" ? message : "Something went wrong running this tool.";
|
|
72
|
+
const kind = error.kind;
|
|
73
|
+
errorKind = typeof kind === "string" && WATCH_ERROR_KINDS.has(kind) ? kind : null;
|
|
65
74
|
}
|
|
66
75
|
return {
|
|
67
76
|
type,
|
|
68
77
|
id: typeof record.id === "string" ? record.id : null,
|
|
69
78
|
result: record.result,
|
|
70
79
|
errorMessage,
|
|
80
|
+
errorKind,
|
|
71
81
|
route: sanitizeAppRoute(record.route),
|
|
72
82
|
};
|
|
73
83
|
}
|
|
@@ -93,7 +103,7 @@ export class SdkCore {
|
|
|
93
103
|
// params is typed loose on purpose: app authors call this from untyped
|
|
94
104
|
// JavaScript, and a null/omitted params should mean "no params", not a
|
|
95
105
|
// crash.
|
|
96
|
-
watch(tool, params, callback) {
|
|
106
|
+
watch(tool, params, callback, options) {
|
|
97
107
|
// Watches are registered by arbitrary app code; a watch() call that
|
|
98
108
|
// leaked into a render loop would otherwise grow forever and turn
|
|
99
109
|
// every data-changed into an unbounded fan-out of real tool calls.
|
|
@@ -104,7 +114,10 @@ export class SdkCore {
|
|
|
104
114
|
}
|
|
105
115
|
this.sequence += 1;
|
|
106
116
|
const id = `w${this.sequence}`;
|
|
107
|
-
|
|
117
|
+
// Options come from untyped app code: a non-function onError is
|
|
118
|
+
// ignored rather than thrown at on the first failure.
|
|
119
|
+
const onError = typeof options?.onError === "function" ? options.onError : null;
|
|
120
|
+
this.watches.set(id, { tool, params: params ?? {}, callback, onError, lastDeliveredJson: null });
|
|
108
121
|
// Before the host pins, requestWatch posts nothing; the pinning hello
|
|
109
122
|
// fires every registered watch, so pre-hello watches are simply held.
|
|
110
123
|
this.requestWatch(id);
|
|
@@ -172,11 +185,12 @@ export class SdkCore {
|
|
|
172
185
|
if (entry === undefined)
|
|
173
186
|
return;
|
|
174
187
|
if (message.errorMessage !== null) {
|
|
175
|
-
const json = JSON.stringify({ error: message.errorMessage });
|
|
188
|
+
const json = JSON.stringify({ error: message.errorMessage, kind: message.errorKind });
|
|
176
189
|
if (json === entry.lastDeliveredJson)
|
|
177
190
|
return;
|
|
178
191
|
entry.lastDeliveredJson = json;
|
|
179
192
|
this.onToolError?.(entry.tool, message.errorMessage);
|
|
193
|
+
entry.onError?.({ message: message.errorMessage, kind: message.errorKind });
|
|
180
194
|
return;
|
|
181
195
|
}
|
|
182
196
|
// Wrapping in an object keeps stringify total: even `result: undefined`
|