@notionhq/custom-blocks-host 0.2.6 → 0.2.7

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
@@ -81,9 +81,10 @@ const host = createCustomBlockHost({
81
81
  onInitialized: state => {
82
82
  console.log("Initialized manifest", state.manifest)
83
83
  },
84
- onFailure: error => {
84
+ onConnectionFailure: error => {
85
85
  showHostError(error)
86
86
  },
87
+ onQueryUnsubscribed: undefined,
87
88
  onLog: (direction, payload) => {
88
89
  console.log(direction, payload)
89
90
  },
@@ -101,8 +102,8 @@ Each host handles one initialization handshake with one sandbox iframe:
101
102
  malformed ones. A connection after an initialization timeout does not restart it.
102
103
  - The first observed iframe `load` is allowed, including when initialization completes before it.
103
104
  - A second observed `load` stops the host and reports retryable `iframe_reloaded` through
104
- `onFailure`. A reload or navigation can replace the sandbox document, so the host ends the
105
- connection instead of continuing to serve the replacement.
105
+ `onConnectionFailure`. A reload or navigation can replace the sandbox document, so the host ends
106
+ the connection instead of continuing to serve the replacement.
106
107
  - Stopping removes message and load listeners, clears initialization timers, ends query
107
108
  subscriptions, and prevents further outbound messages, including pending responses.
108
109
  - To retry or apply a deployment or configuration change, the embedder creates a new host and
@@ -142,8 +143,12 @@ iframe reload.
142
143
 
143
144
  - `onInitialized` reports success after the sandbox accepts initialization. It provides the selected
144
145
  `manifest` and the sandbox's optional `initialHeight`.
145
- - `onFailure` reports a handshake failure, timeout, unavailable page context, or iframe reload. It
146
- can run after initialization succeeds.
146
+ - `onConnectionFailure` reports a handshake failure, timeout, unavailable page context, or iframe
147
+ reload. It can run after initialization succeeds.
148
+
149
+ - `onQueryUnsubscribed` reports when a query subscription ends. Use it to release per-query
150
+ resources, such as listeners or timers. The host removes the subscription itself. A query failure
151
+ can end one subscription while the connection remains active.
147
152
 
148
153
  `queryDataSource` is required. Other request handlers are optional. Every request handler may return
149
154
  its result synchronously or as a promise. Results use a `status` discriminator: successful results
@@ -256,6 +261,15 @@ removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated u
256
261
  are safe. The runtime also ignores results from a query that was unsubscribed while its handler was
257
262
  running.
258
263
 
264
+ A non-retryable query error ends the subscription and discards any queued refresh. This includes
265
+ validation errors before the query handler runs. Retryable errors retain the subscription.
266
+
267
+ Use `onQueryUnsubscribed(subscriptionId)` to release adapter resources such as collection listeners.
268
+ The callback runs when a subscription ends through an explicit unsubscribe, replacement, terminal
269
+ error, or host stop. Repeated removal of the same subscription does not repeat the callback. A late
270
+ result from a replaced query cannot remove its replacement. The sandbox still receives the terminal
271
+ error result.
272
+
259
273
  Call `stop()` when the iframe is removed. It detaches listeners, clears timeouts, and discards
260
274
  active query subscriptions.
261
275
 
@@ -33,7 +33,9 @@ export type CustomBlockHostOptions = {
33
33
  * before this callback runs.
34
34
  * This callback can run after initialization succeeds.
35
35
  */
36
- onFailure?: (error: CustomBlockHostFailure) => void;
36
+ onConnectionFailure: ((error: CustomBlockHostFailure) => void) | undefined;
37
+ /** Releases adapter resources when a query ends, including replacement and terminal errors. */
38
+ onQueryUnsubscribed: ((subscriptionId: string) => void) | undefined;
37
39
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void;
38
40
  getInitialState: CustomBlockHostGetInitialState;
39
41
  initialStateTimeoutMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAA;AAI5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAiBvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAA;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAKzC,OAAO,KAAK,EACX,iCAAiC,EACjC,8BAA8B,EAI9B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EACX,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAe5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IACnD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1E,eAAe,EAAE,8BAA8B,CAAA;IAC/C,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC/D,eAAe,EAAE,CAChB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,KACrD,IAAI,CAAA;IACT,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAK,IAAI,CAAA;IACjE,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAC1E,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;KACzD,KAAK,IAAI,CAAA;CACV,CAAA;AAQD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CAuYvB"}
1
+ {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAA;AAI5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAiBvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAA;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAKzC,OAAO,KAAK,EACX,iCAAiC,EACjC,8BAA8B,EAI9B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EACX,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAe5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC1E,+FAA+F;IAC/F,mBAAmB,EAAE,CAAC,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACnE,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1E,eAAe,EAAE,8BAA8B,CAAA;IAC/C,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC/D,eAAe,EAAE,CAChB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,KACrD,IAAI,CAAA;IACT,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAK,IAAI,CAAA;IACjE,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAC1E,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;KACzD,KAAK,IAAI,CAAA;CACV,CAAA;AAQD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CA8YvB"}
@@ -19,7 +19,7 @@ import { unreachable } from "./utils.js";
19
19
  // Earlier SDKs cannot represent this context, so the host reports context_unavailable.
20
20
  const BRIDGE_PROTOCOL_VERSION_INTRODUCING_UNAVAILABLE_PAGE = 5;
21
21
  export function createCustomBlockHost(options) {
22
- const { iframe, getInitialState, handlers, onManifest, onInitialized, onFailure, onLog, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, initialStateTimeoutMs, noInitResultTimeoutMs, } = options;
22
+ const { iframe, getInitialState, handlers, onManifest, onInitialized, onConnectionFailure, onLog, onQueryUnsubscribed, sandboxOrigin, minBridgeProtocolVersion: configuredMinBridgeProtocolVersion, noConnectTimeoutMs, initialStateTimeoutMs, noInitResultTimeoutMs, } = options;
23
23
  // Callers can retire older SDKs, but cannot restore support for protocols
24
24
  // the runtime no longer implements. Omission uses the runtime minimum.
25
25
  const minBridgeProtocolVersion = Math.max(configuredMinBridgeProtocolVersion ??
@@ -29,7 +29,9 @@ export function createCustomBlockHost(options) {
29
29
  let manifest;
30
30
  let activeDataSources;
31
31
  let sandboxBridgeProtocolVersion = LATEST_BRIDGE_PROTOCOL_VERSION;
32
- const querySubscriptions = new QuerySubscriptions();
32
+ const querySubscriptions = new QuerySubscriptions(subscriptionId => {
33
+ onQueryUnsubscribed?.(subscriptionId);
34
+ });
33
35
  function emit(direction, payload) {
34
36
  onLog?.(direction, payload);
35
37
  }
@@ -114,6 +116,7 @@ export function createCustomBlockHost(options) {
114
116
  const queryError = getQueryValidationError(message, collectionSchema);
115
117
  if (queryError !== undefined) {
116
118
  if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
119
+ querySubscriptions.unsubscribe(message.subscriptionId);
117
120
  postQueryDataSourceResult({
118
121
  subscriptionId: message.subscriptionId,
119
122
  response: { status: "error", error: queryError },
@@ -125,6 +128,9 @@ export function createCustomBlockHost(options) {
125
128
  if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
126
129
  return;
127
130
  }
131
+ if (response.status === "error" && !response.error.isRetryable) {
132
+ querySubscriptions.unsubscribe(message.subscriptionId);
133
+ }
128
134
  postQueryDataSourceResult({
129
135
  subscriptionId: message.subscriptionId,
130
136
  response,
@@ -203,7 +209,7 @@ export function createCustomBlockHost(options) {
203
209
  onInitialized?.({ manifest, initialHeight: message.initialHeight });
204
210
  }
205
211
  },
206
- onFailure,
212
+ onConnectionFailure,
207
213
  });
208
214
  function prepareInitialState(result) {
209
215
  if (result.status === "error") {
@@ -270,7 +276,7 @@ export function createCustomBlockHost(options) {
270
276
  function stopWithFailure(error) {
271
277
  stop();
272
278
  emit("warn", error);
273
- onFailure?.(error);
279
+ onConnectionFailure?.(error);
274
280
  }
275
281
  return {
276
282
  stop,
@@ -15,7 +15,7 @@ export type InitializationControllerOptions = {
15
15
  noInitResultTimeoutMs?: number;
16
16
  stopHostWithFailure: (error: CustomBlockHostFailure) => void;
17
17
  onInitResult?: (message: InitResultMessage) => void;
18
- onFailure?: (error: CustomBlockHostFailure) => void;
18
+ onConnectionFailure: ((error: CustomBlockHostFailure) => void) | undefined;
19
19
  };
20
20
  export type InitializationController = {
21
21
  readonly status: InitStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EACX,wBAAwB,EACxB,iBAAiB,EACjB,MAAM,mDAAmD,CAAA;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAIlE,OAAO,KAAK,EACX,qBAAqB,EACrB,iCAAiC,EACjC,UAAU,EACV,MAAM,YAAY,CAAA;AAMnB,MAAM,MAAM,+BAA+B,GAAG;IAC7C,WAAW,EAAE,CACZ,OAAO,EAAE,qBAAqB,KAC1B,OAAO,CAAC,iCAAiC,CAAC,CAAA;IAC/C,eAAe,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACxE,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChC,4BAA4B,EAAE,MAAM,IAAI,CAAA;IACxC,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAC5D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACnD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;CACnD,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAChD,gBAAgB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACtD,oBAAoB,EAAE,CAAC,IAAI,EAAE;QAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;QACpC,KAAK,EAAE,wBAAwB,CAAA;QAC/B,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAA;KACzC,KAAK,SAAS,GAAG,MAAM,CAAA;IACxB,uBAAuB,EAAE,CAAC,IAAI,EAAE;QAC/B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;QACpC,KAAK,EAAE,wBAAwB,CAAA;KAC/B,KAAK,IAAI,CAAA;IACV,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,IAAI,CAAA;CAChB,CAAA;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,+BAA+B,GACtC,wBAAwB,CAiU1B"}
1
+ {"version":3,"file":"initializationController.d.ts","sourceRoot":"","sources":["../../src/lifecycle/initializationController.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sDAAsD,CAAA;AAC1F,OAAO,KAAK,EACX,wBAAwB,EACxB,iBAAiB,EACjB,MAAM,mDAAmD,CAAA;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAA;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAIlE,OAAO,KAAK,EACX,qBAAqB,EACrB,iCAAiC,EACjC,UAAU,EACV,MAAM,YAAY,CAAA;AAMnB,MAAM,MAAM,+BAA+B,GAAG;IAC7C,WAAW,EAAE,CACZ,OAAO,EAAE,qBAAqB,KAC1B,OAAO,CAAC,iCAAiC,CAAC,CAAA;IAC/C,eAAe,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACxE,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChC,4BAA4B,EAAE,MAAM,IAAI,CAAA;IACxC,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAC5D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACnD,mBAAmB,EAAE,CAAC,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CAC1E,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAChD,gBAAgB,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACtD,oBAAoB,EAAE,CAAC,IAAI,EAAE;QAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;QACpC,KAAK,EAAE,wBAAwB,CAAA;QAC/B,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAA;KACzC,KAAK,SAAS,GAAG,MAAM,CAAA;IACxB,uBAAuB,EAAE,CAAC,IAAI,EAAE;QAC/B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;QACpC,KAAK,EAAE,wBAAwB,CAAA;KAC/B,KAAK,IAAI,CAAA;IACV,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,IAAI,CAAA;CAChB,CAAA;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,+BAA+B,GACtC,wBAAwB,CAiU1B"}
@@ -6,7 +6,7 @@ const DEFAULT_NO_CONNECT_TIMEOUT_MS = 5_000;
6
6
  const DEFAULT_INITIAL_STATE_TIMEOUT_MS = 5_000;
7
7
  const DEFAULT_NO_INIT_RESULT_TIMEOUT_MS = 5_000;
8
8
  export function createInitializationController(options) {
9
- const { prepareInit, sendInitMessage, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, initialStateTimeoutMs = DEFAULT_INITIAL_STATE_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, stopHostWithFailure, onInitResult, onFailure, } = options;
9
+ const { prepareInit, sendInitMessage, warn, flushPendingHostStateChanges, minBridgeProtocolVersion, noConnectTimeoutMs = DEFAULT_NO_CONNECT_TIMEOUT_MS, initialStateTimeoutMs = DEFAULT_INITIAL_STATE_TIMEOUT_MS, noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS, stopHostWithFailure, onInitResult, onConnectionFailure, } = options;
10
10
  let initStatus = "waitingForConnect";
11
11
  let noConnectTimeoutId;
12
12
  let initialStateTimeoutId;
@@ -56,7 +56,7 @@ export function createInitializationController(options) {
56
56
  status: "error",
57
57
  error,
58
58
  });
59
- onFailure?.(error);
59
+ onConnectionFailure?.(error);
60
60
  }
61
61
  async function prepareAndSendInit(connect) {
62
62
  if (initStatus !== "waitingForConnect") {
@@ -110,7 +110,7 @@ export function createInitializationController(options) {
110
110
  }
111
111
  initStatus = "error";
112
112
  const error = initErrorForFailureReason("no_init_result");
113
- onFailure?.(error);
113
+ onConnectionFailure?.(error);
114
114
  warn({
115
115
  ...error,
116
116
  initializationId: connect.initializationId,
@@ -210,7 +210,7 @@ export function createInitializationController(options) {
210
210
  clearNoInitResultTimeout();
211
211
  initStatus = "error";
212
212
  warn(error);
213
- onFailure?.(error);
213
+ onConnectionFailure?.(error);
214
214
  }
215
215
  function onIframeLoad() {
216
216
  if (initStatus === "stopped") {
@@ -238,7 +238,7 @@ export function createInitializationController(options) {
238
238
  initStatus = "error";
239
239
  const error = initErrorForFailureReason("no_connect");
240
240
  warn(error);
241
- onFailure?.(error);
241
+ onConnectionFailure?.(error);
242
242
  }, noConnectTimeoutMs);
243
243
  }
244
244
  function handleInitResult(message) {
@@ -256,7 +256,7 @@ export function createInitializationController(options) {
256
256
  flushPendingHostStateChanges();
257
257
  }
258
258
  else {
259
- onFailure?.(message.error);
259
+ onConnectionFailure?.(message.error);
260
260
  }
261
261
  onInitResult?.(message);
262
262
  }
@@ -8,7 +8,9 @@ export type QuerySubscriptionToken = {
8
8
  readonly _tag: "QuerySubscriptionToken";
9
9
  };
10
10
  export declare class QuerySubscriptions {
11
+ private readonly onUnsubscribe;
11
12
  private readonly bySubscriptionId;
13
+ constructor(onUnsubscribe: (subscriptionId: string) => void);
12
14
  subscribe(message: QueryDataSourceMessage): QuerySubscriptionToken;
13
15
  isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean;
14
16
  unsubscribe(subscriptionId: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AASD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CAWjE;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,GAAG,SAAS,CAmBpE;IAED,MAAM,CACL,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,sBAAsB,GAC3B,YAAY,GAAG,SAAS,CAY1B;IAED,cAAc,SAEb;CACD"}
1
+ {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AASD,qBAAa,kBAAkB;IAI7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAH/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,YACkB,aAAa,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,EAC7D;IAEJ,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CAYjE;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAIxC;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,GAAG,SAAS,CAmBpE;IAED,MAAM,CACL,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,sBAAsB,GAC3B,YAAY,GAAG,SAAS,CAY1B;IAED,cAAc,SAIb;CACD"}
@@ -1,6 +1,11 @@
1
1
  export class QuerySubscriptions {
2
+ onUnsubscribe;
2
3
  bySubscriptionId = new Map();
4
+ constructor(onUnsubscribe) {
5
+ this.onUnsubscribe = onUnsubscribe;
6
+ }
3
7
  subscribe(message) {
8
+ this.unsubscribe(message.subscriptionId);
4
9
  const token = {
5
10
  _tag: "QuerySubscriptionToken",
6
11
  };
@@ -16,7 +21,9 @@ export class QuerySubscriptions {
16
21
  return this.bySubscriptionId.get(subscriptionId)?.token === token;
17
22
  }
18
23
  unsubscribe(subscriptionId) {
19
- this.bySubscriptionId.delete(subscriptionId);
24
+ if (this.bySubscriptionId.delete(subscriptionId)) {
25
+ this.onUnsubscribe(subscriptionId);
26
+ }
20
27
  }
21
28
  refresh(dataSourceId) {
22
29
  const refreshes = [];
@@ -52,6 +59,8 @@ export class QuerySubscriptions {
52
59
  return { message: state.message, token: state.token };
53
60
  }
54
61
  unsubscribeAll() {
55
- this.bySubscriptionId.clear();
62
+ for (const subscriptionId of this.bySubscriptionId.keys()) {
63
+ this.unsubscribe(subscriptionId);
64
+ }
56
65
  }
57
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -83,7 +83,9 @@ export type CustomBlockHostOptions = {
83
83
  * before this callback runs.
84
84
  * This callback can run after initialization succeeds.
85
85
  */
86
- onFailure?: (error: CustomBlockHostFailure) => void
86
+ onConnectionFailure: ((error: CustomBlockHostFailure) => void) | undefined
87
+ /** Releases adapter resources when a query ends, including replacement and terminal errors. */
88
+ onQueryUnsubscribed: ((subscriptionId: string) => void) | undefined
87
89
  onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
88
90
  getInitialState: CustomBlockHostGetInitialState
89
91
  initialStateTimeoutMs?: number
@@ -119,8 +121,9 @@ export function createCustomBlockHost(
119
121
  handlers,
120
122
  onManifest,
121
123
  onInitialized,
122
- onFailure,
124
+ onConnectionFailure,
123
125
  onLog,
126
+ onQueryUnsubscribed,
124
127
  sandboxOrigin,
125
128
  minBridgeProtocolVersion: configuredMinBridgeProtocolVersion,
126
129
  noConnectTimeoutMs,
@@ -143,7 +146,9 @@ export function createCustomBlockHost(
143
146
  let manifest: CustomBlockManifest | undefined
144
147
  let activeDataSources: CustomBlockHostNormalizedDataSourcesPayload | undefined
145
148
  let sandboxBridgeProtocolVersion = LATEST_BRIDGE_PROTOCOL_VERSION
146
- const querySubscriptions = new QuerySubscriptions()
149
+ const querySubscriptions = new QuerySubscriptions(subscriptionId => {
150
+ onQueryUnsubscribed?.(subscriptionId)
151
+ })
147
152
 
148
153
  function emit(direction: CustomBlockHostLogDirection, payload: unknown) {
149
154
  onLog?.(direction, payload)
@@ -255,6 +260,7 @@ export function createCustomBlockHost(
255
260
  const queryError = getQueryValidationError(message, collectionSchema)
256
261
  if (queryError !== undefined) {
257
262
  if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
263
+ querySubscriptions.unsubscribe(message.subscriptionId)
258
264
  postQueryDataSourceResult({
259
265
  subscriptionId: message.subscriptionId,
260
266
  response: { status: "error", error: queryError },
@@ -266,6 +272,9 @@ export function createCustomBlockHost(
266
272
  if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
267
273
  return
268
274
  }
275
+ if (response.status === "error" && !response.error.isRetryable) {
276
+ querySubscriptions.unsubscribe(message.subscriptionId)
277
+ }
269
278
  postQueryDataSourceResult({
270
279
  subscriptionId: message.subscriptionId,
271
280
  response,
@@ -348,7 +357,7 @@ export function createCustomBlockHost(
348
357
  onInitialized?.({ manifest, initialHeight: message.initialHeight })
349
358
  }
350
359
  },
351
- onFailure,
360
+ onConnectionFailure,
352
361
  })
353
362
 
354
363
  function prepareInitialState(
@@ -431,7 +440,7 @@ export function createCustomBlockHost(
431
440
  function stopWithFailure(error: CustomBlockHostFailure) {
432
441
  stop()
433
442
  emit("warn", error)
434
- onFailure?.(error)
443
+ onConnectionFailure?.(error)
435
444
  }
436
445
 
437
446
  return {
@@ -33,7 +33,7 @@ export type InitializationControllerOptions = {
33
33
  noInitResultTimeoutMs?: number
34
34
  stopHostWithFailure: (error: CustomBlockHostFailure) => void
35
35
  onInitResult?: (message: InitResultMessage) => void
36
- onFailure?: (error: CustomBlockHostFailure) => void
36
+ onConnectionFailure: ((error: CustomBlockHostFailure) => void) | undefined
37
37
  }
38
38
 
39
39
  export type InitializationController = {
@@ -68,7 +68,7 @@ export function createInitializationController(
68
68
  noInitResultTimeoutMs = DEFAULT_NO_INIT_RESULT_TIMEOUT_MS,
69
69
  stopHostWithFailure,
70
70
  onInitResult,
71
- onFailure,
71
+ onConnectionFailure,
72
72
  } = options
73
73
 
74
74
  let initStatus: InitStatus = "waitingForConnect"
@@ -127,7 +127,7 @@ export function createInitializationController(
127
127
  status: "error",
128
128
  error,
129
129
  })
130
- onFailure?.(error)
130
+ onConnectionFailure?.(error)
131
131
  }
132
132
 
133
133
  async function prepareAndSendInit(connect: ConnectSuccessMessage) {
@@ -184,7 +184,7 @@ export function createInitializationController(
184
184
  }
185
185
  initStatus = "error"
186
186
  const error = initErrorForFailureReason("no_init_result")
187
- onFailure?.(error)
187
+ onConnectionFailure?.(error)
188
188
  warn({
189
189
  ...error,
190
190
  initializationId: connect.initializationId,
@@ -301,7 +301,7 @@ export function createInitializationController(
301
301
  clearNoInitResultTimeout()
302
302
  initStatus = "error"
303
303
  warn(error)
304
- onFailure?.(error)
304
+ onConnectionFailure?.(error)
305
305
  }
306
306
 
307
307
  function onIframeLoad() {
@@ -331,7 +331,7 @@ export function createInitializationController(
331
331
  initStatus = "error"
332
332
  const error = initErrorForFailureReason("no_connect")
333
333
  warn(error)
334
- onFailure?.(error)
334
+ onConnectionFailure?.(error)
335
335
  }, noConnectTimeoutMs)
336
336
  }
337
337
 
@@ -351,7 +351,7 @@ export function createInitializationController(
351
351
  if (message.status === "success") {
352
352
  flushPendingHostStateChanges()
353
353
  } else {
354
- onFailure?.(message.error)
354
+ onConnectionFailure?.(message.error)
355
355
  }
356
356
  onInitResult?.(message)
357
357
  }
@@ -20,7 +20,12 @@ type QuerySubscriptionState = {
20
20
  export class QuerySubscriptions {
21
21
  private readonly bySubscriptionId = new Map<string, QuerySubscriptionState>()
22
22
 
23
+ constructor(
24
+ private readonly onUnsubscribe: (subscriptionId: string) => void,
25
+ ) {}
26
+
23
27
  subscribe(message: QueryDataSourceMessage): QuerySubscriptionToken {
28
+ this.unsubscribe(message.subscriptionId)
24
29
  const token: QuerySubscriptionToken = {
25
30
  _tag: "QuerySubscriptionToken",
26
31
  }
@@ -38,7 +43,9 @@ export class QuerySubscriptions {
38
43
  }
39
44
 
40
45
  unsubscribe(subscriptionId: string): void {
41
- this.bySubscriptionId.delete(subscriptionId)
46
+ if (this.bySubscriptionId.delete(subscriptionId)) {
47
+ this.onUnsubscribe(subscriptionId)
48
+ }
42
49
  }
43
50
 
44
51
  refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] | undefined {
@@ -80,6 +87,8 @@ export class QuerySubscriptions {
80
87
  }
81
88
 
82
89
  unsubscribeAll() {
83
- this.bySubscriptionId.clear()
90
+ for (const subscriptionId of this.bySubscriptionId.keys()) {
91
+ this.unsubscribe(subscriptionId)
92
+ }
84
93
  }
85
94
  }