@notionhq/custom-blocks-host 0.1.19 → 0.1.21

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.
Files changed (2) hide show
  1. package/README.md +120 -141
  2. package/package.json +25 -25
package/README.md CHANGED
@@ -1,25 +1,25 @@
1
1
  # @notionhq/custom-blocks-host
2
2
 
3
- Host runtime for the custom block bridge. This package owns the generic
4
- `postMessage` protocol loop used by hosts such as the local dev shell. It does
5
- not own iframe policy, permissions, persistence, analytics, or host UI state.
6
- Host implementations such as the dev shell and `notion-next` depend on this package.
3
+ Host runtime for the custom block bridge. This package owns the generic `postMessage` protocol loop
4
+ used by hosts such as the local dev shell. It does not own iframe policy, permissions, persistence,
5
+ analytics, or host UI state. Host implementations such as the dev shell and `notion-next` depend on
6
+ this package.
7
7
 
8
8
  ## `getAutoBoundPropertyIdsByKey()`
9
9
 
10
- `getAutoBoundPropertyIdsByKey()` matches manifest properties to schema
11
- properties for initial property binding. For each manifest property:
10
+ `getAutoBoundPropertyIdsByKey()` matches manifest properties to schema properties for initial
11
+ property binding. For each manifest property:
12
12
 
13
13
  1. Consider only schema properties of the same type.
14
- 2. A schema property is a candidate if its ID equals the manifest key
15
- (exact string: no trim, no case folding) or its display name equals the
16
- manifest display name after `trim()` and lowercasing.
14
+ 2. A schema property is a candidate if its ID equals the manifest key (exact string: no trim, no
15
+ case folding) or its display name equals the manifest display name after `trim()` and
16
+ lowercasing.
17
17
  3. Bind when there is exactly one candidate. Otherwise leave it unmapped.
18
18
 
19
19
  Wrong-type properties never enter the candidate set.
20
20
 
21
21
  ```ts
22
- import { getAutoBoundPropertyIdsByKey } from "@notionhq/custom-blocks-host";
22
+ import { getAutoBoundPropertyIdsByKey } from "@notionhq/custom-blocks-host"
23
23
 
24
24
  const propertyIdsByKey = getAutoBoundPropertyIdsByKey({
25
25
  manifestProperties: {
@@ -30,20 +30,19 @@ const propertyIdsByKey = getAutoBoundPropertyIdsByKey({
30
30
  title: { name: "Different label", type: "title" },
31
31
  "property-1": { name: " estimate ", type: "number" },
32
32
  },
33
- });
33
+ })
34
34
 
35
35
  // { title: "title", estimate: "property-1" }
36
36
  ```
37
37
 
38
38
  ## `createCustomBlockHost()`
39
39
 
40
- `createCustomBlockHost()` attaches a message listener, performs the
41
- `connect` / `init` / `initResult` handshake, validates sandbox messages, routes
42
- requests to handlers, manages query subscriptions, and emits live state
43
- updates.
40
+ `createCustomBlockHost()` attaches a message listener, performs the `connect` / `init` /
41
+ `initResult` handshake, validates sandbox messages, routes requests to handlers, manages query
42
+ subscriptions, and emits live state updates.
44
43
 
45
44
  ```ts
46
- import { createCustomBlockHost } from "@notionhq/custom-blocks-host";
45
+ import { createCustomBlockHost } from "@notionhq/custom-blocks-host"
47
46
 
48
47
  const host = createCustomBlockHost({
49
48
  iframe,
@@ -59,99 +58,90 @@ const host = createCustomBlockHost({
59
58
  dataSources: { bindings },
60
59
  },
61
60
  handlers: {
62
- queryDataSource: async (message) => ({
61
+ queryDataSource: async message => ({
63
62
  status: "success",
64
63
  items: await queryRows(message),
65
64
  hasMore: false,
66
65
  }),
67
- createPage: async (message) => createPage(message),
68
- getPage: async (message) => getPage(message),
69
- openPage: async (message) => openPage(message),
70
- updatePage: async (message) => updatePage(message),
71
- getUser: async (message) => getUser(message),
72
- listUsers: async (message) => listUsers(message),
73
- resize: (message) => resizeIframe(message.height),
66
+ createPage: async message => createPage(message),
67
+ getPage: async message => getPage(message),
68
+ openPage: async message => openPage(message),
69
+ updatePage: async message => updatePage(message),
70
+ getUser: async message => getUser(message),
71
+ listUsers: async message => listUsers(message),
72
+ resize: message => resizeIframe(message.height),
74
73
  },
75
- onManifest: (manifest) => {
76
- console.log("Selected manifest", manifest);
74
+ onManifest: manifest => {
75
+ console.log("Selected manifest", manifest)
77
76
  },
78
- onInitialized: (state) => {
79
- console.log("Initialized manifest", state.manifest);
77
+ onInitialized: state => {
78
+ console.log("Initialized manifest", state.manifest)
80
79
  },
81
- onInitializationFailure: (error) => {
82
- showInitializationError(error);
80
+ onInitializationFailure: error => {
81
+ showInitializationError(error)
83
82
  },
84
83
  onRestartRequired: () => {
85
- remountHostAndIframe();
84
+ remountHostAndIframe()
86
85
  },
87
86
  onLog: (direction, payload) => {
88
- console.log(direction, payload);
87
+ console.log(direction, payload)
89
88
  },
90
- });
89
+ })
91
90
  ```
92
91
 
93
- `minBridgeProtocolVersion` is optional and defaults to the oldest protocol version the host runtime supports.
94
- Set a higher value to retire older SDKs. Lower values cannot enable versions the runtime no longer supports.
95
- The host does not enforce a maximum protocol version.
92
+ `minBridgeProtocolVersion` is optional and defaults to the oldest protocol version the host runtime
93
+ supports. Set a higher value to retire older SDKs. Lower values cannot enable versions the runtime
94
+ no longer supports. The host does not enforce a maximum protocol version.
96
95
 
97
96
  Each host instance initializes once:
98
97
 
99
- - `onManifest` provides the selected manifest as soon as it is available after
100
- `connect`, before binding validation and `onInitialized`. Use it to configure
101
- bindings even when initialization cannot yet succeed.
98
+ - `onManifest` provides the selected manifest as soon as it is available after `connect`, before
99
+ binding validation and `onInitialized`. Use it to configure bindings even when initialization
100
+ cannot yet succeed.
102
101
 
103
- - `onInitialized` reports success after the sandbox accepts initialization. It
104
- provides the selected `manifest` and the sandbox's optional `initialHeight`.
102
+ - `onInitialized` reports success after the sandbox accepts initialization. It provides the selected
103
+ `manifest` and the sandbox's optional `initialHeight`.
105
104
  - `onInitializationFailure` reports an initialization error or timeout.
106
- - `onRestartRequired` reports a connection attempt after the host accepts its
107
- first connection or stops waiting. Recreate the host and iframe together
108
- to accept a new connection.
109
- This can occur after the iframe document changes or a connection arrives
110
- after a timeout.
105
+ - `onRestartRequired` reports a connection attempt after the host accepts its first connection or
106
+ stops waiting. Recreate the host and iframe together to accept a new connection. This can occur
107
+ after the iframe document changes or a connection arrives after a timeout.
111
108
 
112
- The host uses the manifest from `connect`, or `initialState.manifest` if
113
- `connect` omits it. If neither exists, initialization fails with
114
- `manifest_unavailable`.
109
+ The host uses the manifest from `connect`, or `initialState.manifest` if `connect` omits it. If
110
+ neither exists, initialization fails with `manifest_unavailable`.
115
111
 
116
- `queryDataSource` is required. Other request handlers are optional. Every
117
- request handler may return its result synchronously or as a promise. Results
118
- use a `status` discriminator: successful results carry their response payload,
119
- while errors carry `{ code, message, isRetryable }`.
112
+ `queryDataSource` is required. Other request handlers are optional. Every request handler may return
113
+ its result synchronously or as a promise. Results use a `status` discriminator: successful results
114
+ carry their response payload, while errors carry `{ code, message, isRetryable }`.
120
115
 
121
116
  ### Request types
122
117
 
123
- TypeScript infers request types for handlers passed to `createCustomBlockHost()`.
124
- For separately defined handlers and helpers, import the corresponding request
125
- and page-property types directly from `@notionhq/custom-blocks-host`.
118
+ TypeScript infers request types for handlers passed to `createCustomBlockHost()`. For separately
119
+ defined handlers and helpers, import the corresponding request and page-property types directly from
120
+ `@notionhq/custom-blocks-host`.
126
121
 
127
- Request types such as `GetPageMessage` describe validated messages that handlers
128
- receive. The corresponding `*MessageInput` types accept plain string IDs for
129
- adapters and test fixtures that construct requests. These types do not validate
130
- values at runtime. The host runtime validates incoming messages before it calls
131
- handlers. Your implementation remains responsible for permissions and record
132
- access.
122
+ Request types such as `GetPageMessage` describe validated messages that handlers receive. The
123
+ corresponding `*MessageInput` types accept plain string IDs for adapters and test fixtures that
124
+ construct requests. These types do not validate values at runtime. The host runtime validates
125
+ incoming messages before it calls handlers. Your implementation remains responsible for permissions
126
+ and record access.
133
127
 
134
128
  ### Query requests
135
129
 
136
- The `queryDataSource` handler receives raw data source and property IDs. Its
137
- optional `filter` and `sorts` fields have already been resolved by the SDK.
138
- The host validates that sorts contain at most 10 unique property IDs, checks
139
- property IDs and currently supported property types against the bound
140
- collection schema when available, and applies valid sorts in the order provided
141
- by the array.
130
+ The `queryDataSource` handler receives raw data source and property IDs. Its optional `filter` and
131
+ `sorts` fields have already been resolved by the SDK. The host validates that sorts contain at most
132
+ 10 unique property IDs, checks property IDs and currently supported property types against the bound
133
+ collection schema when available, and applies valid sorts in the order provided by the array.
142
134
 
143
- When a data source binding includes `collectionSchema`, the runtime validates
144
- select, multi-select, and status option names before invoking `queryDataSource`.
145
- Bindings without a collection schema cannot perform this check, so their query
146
- handlers remain responsible for validating option names.
135
+ When a data source binding includes `collectionSchema`, the runtime validates select, multi-select,
136
+ and status option names before invoking `queryDataSource`. Bindings without a collection schema
137
+ cannot perform this check, so their query handlers remain responsible for validating option names.
147
138
 
148
- `initialState.dataSources` may also be a function of the successful `connect`
149
- message. This lets a host derive bindings from the sandbox-provided manifest.
139
+ `initialState.dataSources` may also be a function of the successful `connect` message. This lets a
140
+ host derive bindings from the sandbox-provided manifest.
150
141
 
151
- Set `sandboxOrigin` to the iframe's exact origin. The runtime targets outbound
152
- messages to that origin and rejects inbound messages from any other origin.
153
- The runtime also accepts `noConnectTimeoutMs` and `noInitResultTimeoutMs`; both
154
- default to five seconds.
142
+ Set `sandboxOrigin` to the iframe's exact origin. The runtime targets outbound messages to that
143
+ origin and rejects inbound messages from any other origin. The runtime also accepts
144
+ `noConnectTimeoutMs` and `noInitResultTimeoutMs`; both default to five seconds.
155
145
 
156
146
  ### Initialization and malformed messages
157
147
 
@@ -161,42 +151,38 @@ default to five seconds.
161
151
  sandbox connect → host init → sandbox initResult
162
152
  ```
163
153
 
164
- The sandbox creates the `initializationId`. The host copies that ID into `init`.
165
- The sandbox copies it into `initResult`. The host sends live state changes only
166
- after it receives a matching `initResult.success`.
154
+ The sandbox creates the `initializationId`. The host copies that ID into `init`. The sandbox copies
155
+ it into `initResult`. The host sends live state changes only after it receives a matching
156
+ `initResult.success`.
167
157
 
168
- The host must check that the `initializationId` in `initResult` exactly matches
169
- the ID in the `init` message. The host ignores a result with a different ID.
170
- If no result arrives, or only a stale valid result arrives, before the timeout,
171
- initialization ends with `no_init_result`.
158
+ The host must check that the `initializationId` in `initResult` exactly matches the ID in the `init`
159
+ message. The host ignores a result with a different ID. If no result arrives, or only a stale valid
160
+ result arrives, before the timeout, initialization ends with `no_init_result`.
172
161
 
173
- The host validates each inbound sandbox message with `sandboxToHostMessageSchema`
174
- before it handles the message. If parsing fails, the host uses `readIncomingType()`
175
- to identify the attempted message. It replies with `invalidSandboxMessage`, a
176
- negative acknowledgment (NACK). The host never sends a NACK in response to a NACK.
162
+ The host validates each inbound sandbox message with `sandboxToHostMessageSchema` before it handles
163
+ the message. If parsing fails, the host uses `readIncomingType()` to identify the attempted message.
164
+ It replies with `invalidSandboxMessage`, a negative acknowledgment (NACK). The host never sends a
165
+ NACK in response to a NACK.
177
166
 
178
- Malformed sandbox messages do not change the host's initialization state or
179
- timeout, except in these two cases:
167
+ Malformed sandbox messages do not change the host's initialization state or timeout, except in these
168
+ two cases:
180
169
 
181
- - The host expects `connect` and receives a malformed `connect` with a readable
182
- `initializationId`. The host sends `init.error` with `invalid_connect_payload`
183
- instead of a NACK, then fails initialization.
184
- - The host expects `initResult` and receives a malformed `initResult` with the
185
- active `initializationId`. The host sends a NACK and fails initialization with
186
- `invalid_init_result_payload`. This error code describes a local host failure.
187
- The host does not send it in `initResult.error`.
170
+ - The host expects `connect` and receives a malformed `connect` with a readable `initializationId`.
171
+ The host sends `init.error` with `invalid_connect_payload` instead of a NACK, then fails
172
+ initialization.
173
+ - The host expects `initResult` and receives a malformed `initResult` with the active
174
+ `initializationId`. The host sends a NACK and fails initialization with
175
+ `invalid_init_result_payload`. This error code describes a local host failure. The host does not
176
+ send it in `initResult.error`.
188
177
 
189
- A malformed `initResult` with a missing or stale ID does not end initialization
190
- or reset its timeout. Malformed messages do not restart initialization after
191
- it succeeds or fails.
178
+ A malformed `initResult` with a missing or stale ID does not end initialization or reset its
179
+ timeout. Malformed messages do not restart initialization after it succeeds or fails.
192
180
 
193
- The host accepts a valid `initResult.error` as a terminal initialization
194
- failure. It logs and ignores a valid `initResult` with a stale
195
- `initializationId` or an unexpected state.
181
+ The host accepts a valid `initResult.error` as a terminal initialization failure. It logs and
182
+ ignores a valid `initResult` with a stale `initializationId` or an unexpected state.
196
183
 
197
- Hosts do not retry malformed messages. A host that receives a `connect` after
198
- initialization has settled must use `onRestartRequired` to recreate the host
199
- and iframe together.
184
+ Hosts do not retry malformed messages. A host that receives a `connect` after initialization has
185
+ settled must use `onRestartRequired` to recreate the host and iframe together.
200
186
 
201
187
  ## Updating live state
202
188
 
@@ -212,42 +198,35 @@ The returned handle exposes:
212
198
  - `post()`
213
199
  - `stop()`
214
200
 
215
- State updates are sent after `initResult.success`. Updates made earlier are
216
- held until initialization completes. `refreshQuery()` re-runs
217
- `queryDataSource` for every active subscription with the supplied data source
218
- ID, preserving each subscription's filter, sorts, and limit.
201
+ State updates are sent after `initResult.success`. Updates made earlier are held until
202
+ initialization completes. `refreshQuery()` re-runs `queryDataSource` for every active subscription
203
+ with the supplied data source ID, preserving each subscription's filter, sorts, and limit.
219
204
 
220
- The runtime removes a subscription when it receives
221
- `unsubscribeDataSourceQuery`. Repeated unsubscribe messages are safe. The
222
- runtime also ignores results from a query that was unsubscribed while its
223
- handler was running.
205
+ The runtime removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated
206
+ unsubscribe messages are safe. The runtime also ignores results from a query that was unsubscribed
207
+ while its handler was running.
224
208
 
225
- Call `stop()` when the iframe is removed. It detaches listeners, clears
226
- timeouts, and discards active query subscriptions.
209
+ Call `stop()` when the iframe is removed. It detaches listeners, clears timeouts, and discards
210
+ active query subscriptions.
227
211
 
228
212
  ## Wire protocol
229
213
 
230
- `createCustomBlockHost()` implements the protocol: it validates inbound
231
- messages, runs the initialization handshake, correlates requests, manages query
232
- subscriptions, and emits bridge NACKs. Use its state-update methods and
233
- handlers instead of posting bridge messages directly.
234
-
235
- The bridge protocol is maintained in this repository's `protocol/` package.
236
- The published host package includes a compiled copy of that protocol and does
237
- not expose it as a public subpath. Consumers should use the host APIs above
238
- instead of importing protocol implementation files.
239
-
240
- Host source in this repository imports protocol schemas and types from the
241
- workspace package. The publish build rewrites those imports to the bundled
242
- copy.
243
-
244
- Handler implementations must preserve message identity: one-shot operations
245
- return one result with the request's `requestId`, while `queryDataSource` is a
246
- long-lived subscription keyed by `subscriptionId`. The runtime handles the
247
- `connect` → `init` → `initResult` lifecycle, including waiting for
248
- `initResult.success` before live updates.
249
-
250
- Error payloads use `{ code, message, isRetryable }`. The runtime accepts any
251
- string for `code`, including codes that this package does not know yet. Host
252
- code should preserve unknown codes and use `isRetryable` to decide whether a
253
- retry is appropriate.
214
+ `createCustomBlockHost()` implements the protocol: it validates inbound messages, runs the
215
+ initialization handshake, correlates requests, manages query subscriptions, and emits bridge NACKs.
216
+ Use its state-update methods and handlers instead of posting bridge messages directly.
217
+
218
+ The bridge protocol is maintained in this repository's `protocol/` package. The published host
219
+ package includes a compiled copy of that protocol and does not expose it as a public subpath.
220
+ Consumers should use the host APIs above instead of importing protocol implementation files.
221
+
222
+ Host source in this repository imports protocol schemas and types from the workspace package. The
223
+ publish build rewrites those imports to the bundled copy.
224
+
225
+ Handler implementations must preserve message identity: one-shot operations return one result with
226
+ the request's `requestId`, while `queryDataSource` is a long-lived subscription keyed by
227
+ `subscriptionId`. The runtime handles the `connect` → `init` → `initResult` lifecycle, including
228
+ waiting for `initResult.success` before live updates.
229
+
230
+ Error payloads use `{ code, message, isRetryable }`. The runtime accepts any string for `code`,
231
+ including codes that this package does not know yet. Host code should preserve unknown codes and use
232
+ `isRetryable` to decide whether a retry is appropriate.
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
1
  {
2
- "name": "@notionhq/custom-blocks-host",
3
- "version": "0.1.19",
4
- "description": "Reusable host runtime for Notion custom blocks.",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "publishConfig": {
10
- "access": "public"
11
- },
12
- "exports": {
13
- ".": {
14
- "import": "./dist/index.js",
15
- "types": "./dist/index.d.ts"
16
- }
17
- },
18
- "dependencies": {
19
- "valibot": "^1.4.2"
20
- },
21
- "files": [
22
- "src",
23
- "dist",
24
- "README.md"
25
- ]
26
- }
2
+ "name": "@notionhq/custom-blocks-host",
3
+ "version": "0.1.21",
4
+ "description": "Reusable host runtime for Notion custom blocks.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "valibot": "^1.4.2"
20
+ },
21
+ "files": [
22
+ "src",
23
+ "dist",
24
+ "README.md"
25
+ ]
26
+ }