@notionhq/custom-blocks-host 0.3.11 → 0.4.1
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/LICENSE +21 -0
- package/README.md +143 -236
- package/THIRD_PARTY_LICENSES.md +9 -0
- package/dist/autoBindProperties.d.ts +5 -2
- package/dist/autoBindProperties.d.ts.map +1 -1
- package/dist/autoBindProperties.js +5 -2
- package/dist/browser.js +1 -1
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts +1215 -0
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.d.ts.map +1 -0
- package/dist/compatibility/addMetadataReadSchemasForProtocol3To4.js +27 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.d.ts +8 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.d.ts.map +1 -0
- package/dist/compatibility/normalizeHostToSandboxMessage.js +39 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts +11 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.d.ts.map +1 -0
- package/dist/compatibility/normalizePageResultsForProtocol3To5.js +18 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.d.ts +9 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.d.ts.map +1 -0
- package/dist/compatibility/normalizePageResultsForProtocol6.js +18 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.d.ts +1099 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.d.ts.map +1 -0
- package/dist/compatibility/normalizeSandboxToHostMessage.js +9 -0
- package/dist/compatibility/pageForProtocol3To5.d.ts +159 -0
- package/dist/compatibility/pageForProtocol3To5.d.ts.map +1 -0
- package/dist/{lifecycle/legacyPage.js → compatibility/pageForProtocol3To5.js} +50 -9
- package/dist/compatibility/pageResult.d.ts +11 -0
- package/dist/compatibility/pageResult.d.ts.map +1 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts +9 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.d.ts.map +1 -0
- package/dist/compatibility/pageWithNullTextFallbackProperties.js +46 -0
- package/dist/createCustomBlockHost.d.ts +18 -19
- package/dist/createCustomBlockHost.d.ts.map +1 -1
- package/dist/createCustomBlockHost.js +103 -38
- package/dist/index.d.ts +2 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/{localBlockUrl.d.ts → isAllowedLocalPreviewUrl.d.ts} +2 -2
- package/dist/isAllowedLocalPreviewUrl.d.ts.map +1 -0
- package/dist/{localBlockUrl.js → isAllowedLocalPreviewUrl.js} +3 -7
- package/dist/lifecycle/initErrors.d.ts +1 -0
- package/dist/lifecycle/initErrors.d.ts.map +1 -1
- package/dist/lifecycle/initErrors.js +7 -0
- package/dist/lifecycle/initializationController.d.ts +6 -4
- package/dist/lifecycle/initializationController.d.ts.map +1 -1
- package/dist/lifecycle/initializationController.js +24 -15
- package/dist/lifecycle/resolveManifest.d.ts +13 -0
- package/dist/lifecycle/resolveManifest.d.ts.map +1 -0
- package/dist/lifecycle/resolveManifest.js +21 -0
- package/dist/lifecycle/types.d.ts +1637 -17
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js +18 -0
- package/dist/messages/sandboxMessageRouter.d.ts.map +1 -1
- package/dist/messages/sandboxMessageRouter.js +2 -2
- package/dist/protocol/dataSources/dataSourcePage.d.ts +3 -4
- package/dist/protocol/dataSources/dataSourcePage.js +2 -3
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/isLocalBlockServerUrl.d.ts +2 -0
- package/dist/protocol/isLocalBlockServerUrl.js +9 -0
- package/dist/protocol/messages/connect.d.ts +2 -2
- package/dist/protocol/messages/connect.js +2 -2
- package/dist/protocol/messages/createPageResult.d.ts +2 -2
- package/dist/protocol/messages/createPageResult.js +3 -3
- package/dist/protocol/messages/getPage.d.ts +2 -2
- package/dist/protocol/messages/getPage.js +3 -3
- package/dist/protocol/messages/hostToSandbox.d.ts +3 -3
- package/dist/protocol/messages/hostToSandbox.js +10 -10
- package/dist/protocol/messages/init.d.ts +1 -1
- package/dist/protocol/messages/queryDataSource.d.ts +24 -0
- package/dist/protocol/messages/queryDataSource.js +2 -0
- package/dist/protocol/messages/queryDataSourceResult.d.ts +3 -3
- package/dist/protocol/messages/queryDataSourceResult.js +6 -3
- package/dist/protocol/messages/sandboxToHost.d.ts +8 -0
- package/dist/protocol/messages/updatePageResult.d.ts +2 -2
- package/dist/protocol/messages/updatePageResult.js +3 -3
- package/dist/protocol/pages/page.d.ts +2 -2
- package/dist/protocol/pages/page.js +1 -1
- package/dist/protocol/protocolVersion.d.ts +2 -2
- package/dist/protocol/protocolVersion.js +2 -2
- package/dist/protocol.d.ts +9 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +8 -0
- package/docs/bridge/changelog.md +67 -0
- package/docs/bridge/protocol.md +189 -0
- package/docs/bridge/versioning.md +38 -0
- package/docs/compatibility.md +68 -0
- package/docs/handlers.md +199 -0
- package/docs/lifecycle.md +117 -0
- package/package.json +10 -2
- package/src/autoBindProperties.ts +5 -2
- package/src/compatibility/addMetadataReadSchemasForProtocol3To4.ts +36 -0
- package/src/compatibility/normalizeHostToSandboxMessage.ts +54 -0
- package/src/compatibility/normalizePageResultsForProtocol3To5.ts +28 -0
- package/src/compatibility/normalizePageResultsForProtocol6.ts +23 -0
- package/src/compatibility/normalizeSandboxToHostMessage.ts +10 -0
- package/src/{lifecycle/legacyPage.ts → compatibility/pageForProtocol3To5.ts} +52 -11
- package/src/compatibility/pageResult.ts +18 -0
- package/src/compatibility/pageWithNullTextFallbackProperties.ts +53 -0
- package/src/createCustomBlockHost.ts +152 -62
- package/src/index.ts +4 -51
- package/src/{localBlockUrl.ts → isAllowedLocalPreviewUrl.ts} +4 -7
- package/src/lifecycle/initErrors.ts +9 -0
- package/src/lifecycle/initializationController.ts +34 -19
- package/src/lifecycle/resolveManifest.ts +31 -0
- package/src/lifecycle/types.ts +84 -16
- package/src/messages/sandboxMessageRouter.ts +2 -2
- package/src/protocol.ts +8 -0
- package/dist/lifecycle/legacyPage.d.ts +0 -70
- package/dist/lifecycle/legacyPage.d.ts.map +0 -1
- package/dist/lifecycle/loadCustomBlockManifest.d.ts +0 -16
- package/dist/lifecycle/loadCustomBlockManifest.d.ts.map +0 -1
- package/dist/lifecycle/loadCustomBlockManifest.js +0 -65
- package/dist/lifecycle/manifestOptions.d.ts +0 -17
- package/dist/lifecycle/manifestOptions.d.ts.map +0 -1
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +0 -6
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +0 -1
- package/dist/lifecycle/normalizeHostToSandboxMessage.js +0 -14
- package/dist/lifecycle/normalizePage.d.ts +0 -7
- package/dist/lifecycle/normalizePage.d.ts.map +0 -1
- package/dist/lifecycle/normalizePage.js +0 -47
- package/dist/localBlockUrl.d.ts.map +0 -1
- package/src/lifecycle/loadCustomBlockManifest.ts +0 -89
- package/src/lifecycle/manifestOptions.ts +0 -19
- package/src/lifecycle/normalizeHostToSandboxMessage.ts +0 -27
- package/src/lifecycle/normalizePage.ts +0 -71
- /package/dist/{lifecycle/manifestOptions.js → compatibility/pageResult.js} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Bridge protocol changelog
|
|
2
|
+
|
|
3
|
+
The bridge protocol defines the messages exchanged by a custom block's SDK and its host. This
|
|
4
|
+
changelog records breaking contract changes and the SDK releases that introduced them. It also
|
|
5
|
+
describes compatibility exceptions within a declared protocol version and their effects on blocks.
|
|
6
|
+
Protocol version rules are in [Bridge protocol versioning](versioning.md). The host's
|
|
7
|
+
[compatibility reference](../compatibility.md) describes current support and response
|
|
8
|
+
conversions.
|
|
9
|
+
|
|
10
|
+
Publication dates refer to npm releases in Pacific time, not deployment in Notion. Unqualified SDK
|
|
11
|
+
versions refer to `@notionhq/custom-blocks`.
|
|
12
|
+
|
|
13
|
+
| Protocol | SDK release | Published | Breaking contract changes |
|
|
14
|
+
| ------------------------------------------------------ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| 1 | `ncblock@0.0.1` | May 11, 2026 | Initial `ready` / `init` handshake and block APIs |
|
|
16
|
+
| 2 | `ncblock@0.0.36` | June 18, 2026 | Structured errors, handshake changes, and raw data-source IDs instead of manifest keys |
|
|
17
|
+
| 3 | `0.1.0` | July 22, 2026 | Accumulated contract changes include `connect → init → initResult`, initialization identity, subscription identity, and required structured connect errors |
|
|
18
|
+
| 4 | `0.2.0` | September 17, 2026 | Required `init.contrastMode` and page/query `is_archived` and `in_trash` fields |
|
|
19
|
+
| 5 [†](#protocol-5-unavailable-containing-page-context) | `0.2.16` | September 30, 2026 | Query filters and sorts must address actual database properties. Filter and sort payloads reject extra fields |
|
|
20
|
+
| 6 [‡](#protocol-6-richer-fallback-properties) | `0.3.0` | October 26, 2026 | Typed, display-name-keyed query properties and unified page responses replace the older query representation |
|
|
21
|
+
| 7 | `0.3.3` | October 30, 2026 | Typed text fallback properties for unique ID, button, formula, location, rollup, and verification |
|
|
22
|
+
| 8 | `0.4.0` | November 2, 2026 | Local manifests move from `/manifest` to HTML metadata sent in `connect`; `manifestSource` selects the initialization manifest |
|
|
23
|
+
|
|
24
|
+
The table lists SDK release boundaries, not complete supported version ranges. Host compatibility
|
|
25
|
+
depends on `connect.bridgeProtocolVersion`, not the SDK package version.
|
|
26
|
+
|
|
27
|
+
## Protocol 8: local manifest declarations
|
|
28
|
+
|
|
29
|
+
The dev shell removes the `/manifest` route and embeds the local manifest in block HTML. The SDK
|
|
30
|
+
reads that metadata and sends the declaration in `connect.manifest`; initialization no longer
|
|
31
|
+
fetches `/manifest`.
|
|
32
|
+
|
|
33
|
+
The host option `manifestSource` selects the initialization manifest: `"sandbox"` uses the
|
|
34
|
+
declaration from `connect`, and `"host"` uses the persisted manifest returned by
|
|
35
|
+
`getPersistedManifest`. Only the selected source determines initialization. Deployed blocks using
|
|
36
|
+
supported earlier protocols retain their existing response formats.
|
|
37
|
+
|
|
38
|
+
The shared host runtime minimum remains 3. The dev shell uses the Worker manifest in host mode and
|
|
39
|
+
accepts supported earlier protocols. Sandbox-mode initialization uses the protocol 8 declaration
|
|
40
|
+
from `connect`.
|
|
41
|
+
|
|
42
|
+
## Changes within a declared version
|
|
43
|
+
|
|
44
|
+
### Protocol 5: unavailable containing-page context
|
|
45
|
+
|
|
46
|
+
On October 2, 2026, SDK 0.2.17 added `{ type: "unavailable" }` to containing-page context while
|
|
47
|
+
retaining protocol 5. SDK 0.2.16 rejects that shape. The host preserves later protocol 5 behavior
|
|
48
|
+
and accepts the limitation for the older SDK, as decided on October 31, 2026. Affected blocks must
|
|
49
|
+
upgrade and rebuild. The host's [compatibility reference](../compatibility.md)
|
|
50
|
+
describes current compatibility behavior.
|
|
51
|
+
|
|
52
|
+
### Protocol 6: richer fallback properties
|
|
53
|
+
|
|
54
|
+
The protocol version changed in code on October 22, 2026. On October 26, the `pages` and
|
|
55
|
+
`dataSources` APIs adopted a shared page representation: data-source query rows and page operation
|
|
56
|
+
results use the same page shape, with typed properties keyed by display name.
|
|
57
|
+
|
|
58
|
+
Some SDKs added text fallback property objects for unique ID, button, formula, location, rollup, and
|
|
59
|
+
verification while still reporting protocol 6. Earlier protocol 6 SDKs reject those property
|
|
60
|
+
objects. Because both groups declare the same protocol version, updated hosts send `null` for these
|
|
61
|
+
properties to every protocol 6 block.
|
|
62
|
+
|
|
63
|
+
Blocks built with the later protocol 6 SDKs therefore lose those property values when their host
|
|
64
|
+
updates. UI that displays the values can become empty, and code that assumes a property object is
|
|
65
|
+
present can fail. Upgrade the SDK to protocol 7 or later and rebuild the block to receive the text
|
|
66
|
+
fallback property objects. Host adapters continue returning the current page shape; the host runtime
|
|
67
|
+
applies the protocol-specific conversion.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Bridge wire protocol
|
|
2
|
+
|
|
3
|
+
The bridge carries initialization, data requests, results, and live updates between a custom block's
|
|
4
|
+
SDK and its host. This reference defines the handshake, message identity, validation, and payload
|
|
5
|
+
semantics for host integrators and bridge maintainers. Block authors use the SDK's public APIs.
|
|
6
|
+
|
|
7
|
+
Messages are `postMessage` payloads defined by shared Valibot schemas, exposed through
|
|
8
|
+
`@notionhq/custom-blocks-host/protocol`. The embedding application enforces permissions and record
|
|
9
|
+
access. Version-specific formats and conversions are described in
|
|
10
|
+
[Versioning and compatibility](versioning.md).
|
|
11
|
+
|
|
12
|
+
## Initialization
|
|
13
|
+
|
|
14
|
+
The handshake is:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
sandbox connect → host init → sandbox initResult
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
All three messages carry the sandbox-generated `initializationId`. A sandbox document sends
|
|
21
|
+
`connect` once. The host sends one `init` for the accepted connection and ignores subsequent
|
|
22
|
+
`connect` messages, including malformed ones. Live updates wait for `initResult.success`.
|
|
23
|
+
`init.error` and `initResult.error` are terminal.
|
|
24
|
+
|
|
25
|
+
The sandbox waits for `init` without a timeout. If host state preparation times out, the host sends
|
|
26
|
+
`init.error` with `initial_state_timeout` to end that wait.
|
|
27
|
+
|
|
28
|
+
`connect` carries connection identity, version metadata, and an optional local manifest declaration.
|
|
29
|
+
The SDK reads that declaration from the `application/json` script with ID
|
|
30
|
+
`notion-custom-block-manifest`, injected into the block HTML by dev tooling. It reads metadata only
|
|
31
|
+
from supported local block server URLs. Missing metadata omits `connect.manifest`; invalid metadata
|
|
32
|
+
sends `connect.error` with `manifest_invalid`.
|
|
33
|
+
|
|
34
|
+
The embedding application selects the manifest source. In host mode, the host uses its persisted
|
|
35
|
+
manifest and ignores the sandbox declaration. In sandbox mode, the host uses `connect.manifest` from
|
|
36
|
+
a permitted local sandbox; a missing declaration produces `manifest_unavailable`. The selected
|
|
37
|
+
manifest is authoritative and is returned in `init` with context and data-source bindings. Errors
|
|
38
|
+
from the selected manifest source pass through unchanged. Host mode can initialize after a sandbox
|
|
39
|
+
`manifest_invalid` error using its own manifest; other connect errors remain fatal. The connect
|
|
40
|
+
parser discards unknown properties.
|
|
41
|
+
|
|
42
|
+
`initResult.success` may include `initialHeight`, the height of the sandbox's rendered content in
|
|
43
|
+
CSS pixels. The host's [lifecycle guide](../lifecycle.md) describes iframe sizing,
|
|
44
|
+
reveal, and retry behavior.
|
|
45
|
+
|
|
46
|
+
`connect.bridgeProtocolVersion` declares the sandbox's wire contract. `sdkVersion` is analytics
|
|
47
|
+
metadata. See [versioning and compatibility](versioning.md) for supported formats and
|
|
48
|
+
[Validation and errors](#validation-and-errors) for malformed handshake behavior.
|
|
49
|
+
|
|
50
|
+
## Manifests
|
|
51
|
+
|
|
52
|
+
A manifest declares a block's data sources and display metadata. `manifestSchema` defines its fields
|
|
53
|
+
and defaults omitted `dataSources` and each data source's omitted `properties` to `{}`.
|
|
54
|
+
|
|
55
|
+
`isLocalBlockServerUrl(url)` accepts HTTP(S) URLs on `localhost`, `*.localhost`, or `127.0.0.1`
|
|
56
|
+
without credentials. It checks the address only. The host also requires a sandbox-mode block to use
|
|
57
|
+
a different origin from its embedding page.
|
|
58
|
+
|
|
59
|
+
## Message identity
|
|
60
|
+
|
|
61
|
+
- One-shot request/result pairs use `requestId`.
|
|
62
|
+
- `queryDataSource` is a long-lived subscription; requests and every update use its stable
|
|
63
|
+
`subscriptionId`.
|
|
64
|
+
- The initialization lifecycle uses `initializationId`, not `requestId` or `subscriptionId`.
|
|
65
|
+
- One-way messages, such as `resize` and bridge NACKs, have no correlation ID.
|
|
66
|
+
|
|
67
|
+
Hosts key query state by `subscriptionId`, replace state when a request reuses one, and suppress
|
|
68
|
+
stale asynchronous results.
|
|
69
|
+
|
|
70
|
+
The sandbox can send `unsubscribeDataSourceQuery` with the same `subscriptionId` when it no longer
|
|
71
|
+
needs the subscription. An unknown or repeated unsubscribe is a no-op. Hosts stop future refreshes
|
|
72
|
+
and suppress results from work already in progress.
|
|
73
|
+
|
|
74
|
+
## Validation and errors
|
|
75
|
+
|
|
76
|
+
Each direction has a validation schema:
|
|
77
|
+
|
|
78
|
+
- Sandbox to host: `sandboxToHostMessageSchema`
|
|
79
|
+
- Host to sandbox: `hostToSandboxMessageSchema`
|
|
80
|
+
|
|
81
|
+
On parse failure, the host replies with `invalidSandboxMessage` and the sandbox replies with
|
|
82
|
+
`invalidHostMessage`, except for the handshake cases below. These replies are negative
|
|
83
|
+
acknowledgments (NACKs). Receivers do not NACK another NACK.
|
|
84
|
+
|
|
85
|
+
Malformed sandbox messages do not change the host's initialization state or timeout, except as
|
|
86
|
+
follows:
|
|
87
|
+
|
|
88
|
+
- The host expects `connect` and receives a malformed `connect` with a readable `initializationId`.
|
|
89
|
+
The host sends `init.error` instead of a NACK, then fails initialization. An invalid or
|
|
90
|
+
unsupported numeric protocol version takes precedence over `invalid_connect_payload`.
|
|
91
|
+
- The host expects `initResult` and receives a malformed `initResult` with the active
|
|
92
|
+
`initializationId`. The host sends a NACK and fails initialization with
|
|
93
|
+
`invalid_init_result_payload`. This error code describes a local host failure. The host does not
|
|
94
|
+
send it in `initResult.error`.
|
|
95
|
+
|
|
96
|
+
A malformed `initResult` with a missing or stale ID does not end initialization or reset its
|
|
97
|
+
timeout. Malformed messages do not restart initialization after it succeeds or fails.
|
|
98
|
+
|
|
99
|
+
The sandbox handles a malformed `init` differently if it can verify the `initializationId`. In that
|
|
100
|
+
case, it sends `initResult.error` with `invalid_init_payload` instead of a NACK. Otherwise, it sends
|
|
101
|
+
`invalidHostMessage`.
|
|
102
|
+
|
|
103
|
+
`CustomBlockErrorInfo` carries `code`, `message`, and `isRetryable`. Error schemas accept any string
|
|
104
|
+
for `code`, including unknown codes. TypeScript aliases list known codes for autocomplete.
|
|
105
|
+
|
|
106
|
+
## Data sources
|
|
107
|
+
|
|
108
|
+
### Bindings and IDs
|
|
109
|
+
|
|
110
|
+
Hosts send `NotionDataSourceBindings` as `dataSources: { bindings }`, keyed by the semantic data
|
|
111
|
+
source key declared in the manifest. `collectionSchema` contains only actual database properties.
|
|
112
|
+
|
|
113
|
+
Sandbox-to-host object references are raw IDs. The SDK resolves semantic data-source and property
|
|
114
|
+
keys before sending query or page-operation requests. Get and update requests can carry an optional
|
|
115
|
+
resolved `dataSourceId`.
|
|
116
|
+
|
|
117
|
+
### Query requests
|
|
118
|
+
|
|
119
|
+
`queryDataSource` may include a resolved `filter` and an ordered `sorts` list. Each sort contains a
|
|
120
|
+
raw `propertyId` and an `ascending` or `descending` direction. Hosts apply sorts in array order. The
|
|
121
|
+
SDK and host validate that the list contains at most ten unique properties. The host rejects
|
|
122
|
+
property types without a supported sort value.
|
|
123
|
+
|
|
124
|
+
Every filter and sort property ID must exist in `collectionSchema.propertiesById`. Queries with
|
|
125
|
+
filters or sorts return `data_source_schema_unavailable` when the schema is unavailable. Filter
|
|
126
|
+
branches, operators, person references, and sort objects reject extra fields.
|
|
127
|
+
|
|
128
|
+
#### Creator and status filters
|
|
129
|
+
|
|
130
|
+
Creator filters require `propertyId` to identify an actual database property of type `created_by`.
|
|
131
|
+
The `created_by` filter branch selects the property type, while `propertyId` addresses the column.
|
|
132
|
+
|
|
133
|
+
The `created_by` filter supports human user targets with `contains` and `does_not_contain`, plus
|
|
134
|
+
`is_empty` and `is_not_empty`. Hosts use row creator metadata, including non-human and missing
|
|
135
|
+
creators in exclusion results.
|
|
136
|
+
|
|
137
|
+
Status filters support option operators and stored group names. Group operators accept one name or
|
|
138
|
+
an array of 1–5 names. Hosts must validate each name against the bound Status property.
|
|
139
|
+
|
|
140
|
+
#### Relation filters
|
|
141
|
+
|
|
142
|
+
Relation filters require an actual `relation` property in the bound schema. `contains` and
|
|
143
|
+
`does_not_contain` accept one page ID. `is_empty` and `is_not_empty` accept only `true` and require
|
|
144
|
+
no page ID. Each relation filter accepts exactly one operator. Adapters must evaluate empty checks
|
|
145
|
+
against relation values, including XLDB row IDs where supported.
|
|
146
|
+
|
|
147
|
+
#### Date filters
|
|
148
|
+
|
|
149
|
+
Date comparisons accept ISO values and relative values such as `today`. The query schema defines the
|
|
150
|
+
accepted values and range operators. Range operators require an empty object.
|
|
151
|
+
|
|
152
|
+
Date comparisons accept `target` inside `date`, beside the operator. The target is `start_date` by
|
|
153
|
+
default. For ranges, `end_date` compares the range end. For individual dates, it compares the date.
|
|
154
|
+
Empty checks accept either target, but they test the whole property.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
filter: {
|
|
158
|
+
propertyId: "schedule-id",
|
|
159
|
+
date: { target: "end_date", on_or_after: "2026-10-08" },
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Page context
|
|
164
|
+
|
|
165
|
+
Containing-page context uses `CustomBlockPage`. The `init.page` and `pageChanged.page` fields
|
|
166
|
+
contain `{ type: "page", id, parent }` for an accessible page, or `{ type: "unavailable" }` when the
|
|
167
|
+
containing page itself is inaccessible. The unavailable shape never carries a page ID. This is
|
|
168
|
+
distinct from an accessible page with an inaccessible parent
|
|
169
|
+
(`page.parent: { type: "unavailable" }`).
|
|
170
|
+
|
|
171
|
+
## Page writes
|
|
172
|
+
|
|
173
|
+
The `createPage` and `updatePage` messages use raw property IDs from Notion as map keys. Each value
|
|
174
|
+
must carry the same raw property ID in its `id` field.
|
|
175
|
+
|
|
176
|
+
## Page responses
|
|
177
|
+
|
|
178
|
+
Host handlers return unified `NotionPageData` for queries and page operations. Properties use
|
|
179
|
+
display names as keys and typed values that include their raw property IDs. Only actual schema
|
|
180
|
+
properties are returned. Query results contain `items` and `hasMore`. `notionPageDataSchema` defines
|
|
181
|
+
the page fields, including archive state and creation, edit, and visit metadata.
|
|
182
|
+
|
|
183
|
+
Page reads return string values for `unique_id`, `formula`, `rollup`, `button`, `verification`, and
|
|
184
|
+
`location`, preserving each property's type. These properties are read-only. Values use stored text,
|
|
185
|
+
or `""` when absent. Hosts do not evaluate formulas or rollups.
|
|
186
|
+
|
|
187
|
+
The host converts responses to the sandbox's declared protocol. See
|
|
188
|
+
[versioning and compatibility](versioning.md) for protocol version rules. The host's
|
|
189
|
+
[compatibility reference](../compatibility.md) describes older response formats.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Bridge protocol versioning
|
|
2
|
+
|
|
3
|
+
The SDK inside a block and the host in its embedding application release independently. A bridge
|
|
4
|
+
protocol version identifies the wire contract they use, independently of their package versions.
|
|
5
|
+
This reference defines the version identifiers, protocol format names, and rules for contract
|
|
6
|
+
changes. Release boundaries and exceptions are recorded in the [changelog](changelog.md). The host's
|
|
7
|
+
[compatibility reference](../compatibility.md) describes runtime support and response
|
|
8
|
+
conversions.
|
|
9
|
+
|
|
10
|
+
## Version identifiers
|
|
11
|
+
|
|
12
|
+
- **Bridge protocol:** `connect.bridgeProtocolVersion` declares the sandbox's wire contract.
|
|
13
|
+
- **SDK package:** identifies the SDK's public API and implementation. It can change without a wire
|
|
14
|
+
change.
|
|
15
|
+
- **Host package:** identifies the runtime implementation. Publishing it does not deploy it to
|
|
16
|
+
Notion.
|
|
17
|
+
|
|
18
|
+
`connect.bridgeProtocolVersion` selects the wire contract. `sdkVersion` is analytics metadata.
|
|
19
|
+
`LATEST_BRIDGE_PROTOCOL_VERSION` identifies the latest contract defined by the package.
|
|
20
|
+
|
|
21
|
+
## Protocol export names
|
|
22
|
+
|
|
23
|
+
The `@notionhq/custom-blocks-host/protocol` entrypoint exports wire definitions. Page formats used
|
|
24
|
+
by protocols 3–5 are named `NotionPageForProtocol3To5` and `NotionDataSourcePageForProtocol3To5`,
|
|
25
|
+
with matching `notionPageForProtocol3To5Schema` and `notionDataSourcePageForProtocol3To5Schema`
|
|
26
|
+
validators. Message types and schemas containing `WithProtocol3To5Compatibility` accept both current
|
|
27
|
+
page formats and protocol 3–5 page formats. `UNIFIED_PAGE_PROTOCOL_VERSION` is 6, the minimum
|
|
28
|
+
protocol version that uses unified pages.
|
|
29
|
+
|
|
30
|
+
## Version changes
|
|
31
|
+
|
|
32
|
+
A breaking wire change increases the bridge protocol version. This includes changed meanings,
|
|
33
|
+
required fields, and new response variants an older receiver rejects. Optional fields and new
|
|
34
|
+
messages need no bump only when older receivers can safely ignore or NACK them. Parsing success
|
|
35
|
+
alone does not prove compatible SDK behavior.
|
|
36
|
+
|
|
37
|
+
An SDK that requires a newer protocol needs a host that supports that contract. Updating the SDK
|
|
38
|
+
package does not update the host in the embedding application.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Host compatibility
|
|
2
|
+
|
|
3
|
+
A host can update while deployed blocks keep older SDKs. The runtime accepts supported protocol
|
|
4
|
+
versions and converts current handler results to each block's declared contract. This reference
|
|
5
|
+
covers version admission, normalization, and the formats sent to older blocks. Protocol version
|
|
6
|
+
rules and release history are in the [versioning guide](bridge/versioning.md) and
|
|
7
|
+
[changelog](bridge/changelog.md).
|
|
8
|
+
|
|
9
|
+
## Supported versions
|
|
10
|
+
|
|
11
|
+
The protocol subpath exports `LATEST_BRIDGE_PROTOCOL_VERSION` and
|
|
12
|
+
`MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION`. These constants define the package's latest known
|
|
13
|
+
protocol and runtime minimum. Embedders can raise the minimum but cannot enable retired versions.
|
|
14
|
+
The host admits future positive integer versions and validates them against known schemas. Admission
|
|
15
|
+
does not guarantee that future features work.
|
|
16
|
+
|
|
17
|
+
`sdkVersion` is analytics metadata. Compatibility behavior depends on the protocol version.
|
|
18
|
+
|
|
19
|
+
The current SDK declares protocol 8. The shared runtime minimum is 3. The dev shell uses the Worker
|
|
20
|
+
manifest in host mode and accepts the shared runtime's supported protocol versions. Protocols 7 and
|
|
21
|
+
8 use the same page and query response formats. Protocol 8's local manifest declaration is selected
|
|
22
|
+
only in sandbox mode; host mode uses the persisted manifest. See the
|
|
23
|
+
[protocol 8 release entry](bridge/changelog.md#protocol-8-local-manifest-declarations).
|
|
24
|
+
|
|
25
|
+
The `minBridgeProtocolVersion` option defaults to the runtime minimum. Raising the option rejects
|
|
26
|
+
older SDKs for this embedding application. Lowering it cannot restore retired versions. Raising
|
|
27
|
+
`MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION` retires those versions across the runtime.
|
|
28
|
+
|
|
29
|
+
## Host normalization
|
|
30
|
+
|
|
31
|
+
The host validates sandbox requests before dispatching them to handlers. Handlers use current data
|
|
32
|
+
shapes, and the host converts responses to the sandbox's declared protocol. Setters, query
|
|
33
|
+
refreshes, and handler responses use the same conversion. Unchanged messages and errors pass
|
|
34
|
+
through.
|
|
35
|
+
|
|
36
|
+
## Current compatibility behavior
|
|
37
|
+
|
|
38
|
+
| Target | Query results | Page operation results | Binding schemas |
|
|
39
|
+
| ------ | --------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
40
|
+
| 3–4 | Flat ID-keyed values, including synthetic metadata entries | ID-keyed properties with unsupported property objects omitted | Synthetic metadata read schemas restored in outgoing copies |
|
|
41
|
+
| 5 | Same query values as protocols 3–4 | Same page format as protocols 3–4 | Actual database schemas. The SDK supplies synthetic read schemas locally |
|
|
42
|
+
| 6 | Unified name-keyed properties. Text fallback property objects become `null` | Same unified page format | Actual database schemas |
|
|
43
|
+
| 7+ | Unified pages with typed text fallback properties | Same unified page format | Actual database schemas |
|
|
44
|
+
|
|
45
|
+
The synthetic metadata IDs are `created_time`, `last_edited_time`, `created_by`, and
|
|
46
|
+
`last_edited_by`. They retain metadata meaning if an actual property uses the same raw ID. This
|
|
47
|
+
matches the protocol 3–4 binding schema precedence. Host validation always uses the unmodified
|
|
48
|
+
actual database schema.
|
|
49
|
+
|
|
50
|
+
Release-specific compatibility limits are documented in the changelog entries for
|
|
51
|
+
[protocol 5 page context](bridge/changelog.md#protocol-5-unavailable-containing-page-context)
|
|
52
|
+
and
|
|
53
|
+
[protocol 6 text fallback properties](bridge/changelog.md#protocol-6-richer-fallback-properties).
|
|
54
|
+
|
|
55
|
+
### Unavailable containing-page context
|
|
56
|
+
|
|
57
|
+
Protocols 3–4 receive `context_unavailable` during initialization or stop when containing-page
|
|
58
|
+
context becomes unavailable. This concerns `init.page` and `pageChanged.page`, not a page
|
|
59
|
+
operation's `page_unavailable` error.
|
|
60
|
+
|
|
61
|
+
### Protocol 3–5 metadata query semantics
|
|
62
|
+
|
|
63
|
+
Page creation and edit metadata is readable without corresponding database columns. Current page
|
|
64
|
+
objects carry these fields at the top level. Protocol 3–5 query adapters copy them into
|
|
65
|
+
`propertiesById` for older SDKs.
|
|
66
|
+
|
|
67
|
+
Filters and sorts require actual database properties in the bound schema. The host does not support
|
|
68
|
+
queries against synthetic metadata IDs without corresponding database properties.
|
package/docs/handlers.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Host handlers and data
|
|
2
|
+
|
|
3
|
+
Handlers connect block requests to your application's data access, persistence, and navigation. The
|
|
4
|
+
runtime validates bridge messages and manages subscriptions. Your application enforces viewer
|
|
5
|
+
permissions and implements the requested operations. This guide covers handler contracts, a
|
|
6
|
+
data-source example, refreshes, and external navigation.
|
|
7
|
+
|
|
8
|
+
The [dev shell](https://www.npmjs.com/package/@notionhq/custom-blocks-dev-shell) is a working host
|
|
9
|
+
implementation with local sample data. Use it as a reference for a larger integration. Production
|
|
10
|
+
hosts must provide their own permissions, data access, and persistence.
|
|
11
|
+
|
|
12
|
+
## Handler contracts
|
|
13
|
+
|
|
14
|
+
`queryDataSource` is required. Other handlers are optional. Omitting a one-shot request handler
|
|
15
|
+
sends no response to that request. A handler returns a result synchronously or as a promise. Errors
|
|
16
|
+
carry `{ code, message, isRetryable }`, with open string codes.
|
|
17
|
+
|
|
18
|
+
Handler requests are validated messages with branded IDs. Initial state, setters, handler results,
|
|
19
|
+
and `*MessageInput` types accept plain string IDs. The runtime validates these inputs and applies
|
|
20
|
+
schema defaults. ID brands do not check record existence or access.
|
|
21
|
+
|
|
22
|
+
Query filters and sorts are validated against the actual bound database schema before
|
|
23
|
+
`queryDataSource` runs. See the [wire reference](bridge/protocol.md#data-sources) for
|
|
24
|
+
query semantics.
|
|
25
|
+
|
|
26
|
+
## Connect a data source
|
|
27
|
+
|
|
28
|
+
Connect an embedded block to an in-memory Tasks data source with one numeric Estimate property. This
|
|
29
|
+
walkthrough extends the [README's embed example](../README.md#create-a-host): declare the data
|
|
30
|
+
source, bind its keys to IDs, return a query result, and refresh that result after a value changes.
|
|
31
|
+
|
|
32
|
+
Keep the iframe, context, and lifecycle callbacks from the embed example. The block must declare the
|
|
33
|
+
same manifest and query `tasks` through the SDK's public APIs.
|
|
34
|
+
|
|
35
|
+
### Declare and bind the data source
|
|
36
|
+
|
|
37
|
+
The manifest uses block-defined keys. Bindings connect those keys to actual data-source and property
|
|
38
|
+
IDs. Add these definitions before creating the host:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import type {
|
|
42
|
+
CustomBlockHostDataSourcesPayload,
|
|
43
|
+
CustomBlockHostHandlers,
|
|
44
|
+
} from "@notionhq/custom-blocks-host"
|
|
45
|
+
import type { CustomBlockManifest, NotionPageData } from "@notionhq/custom-blocks-host/protocol"
|
|
46
|
+
|
|
47
|
+
const tasksId = "44444444-4444-4444-8444-444444444444"
|
|
48
|
+
|
|
49
|
+
const manifest: CustomBlockManifest = {
|
|
50
|
+
version: 1,
|
|
51
|
+
dataSources: {
|
|
52
|
+
tasks: {
|
|
53
|
+
name: "Tasks",
|
|
54
|
+
properties: {
|
|
55
|
+
estimate: { name: "Estimate", type: "number" },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const taskBindings: CustomBlockHostDataSourcesPayload = {
|
|
62
|
+
bindings: {
|
|
63
|
+
tasks: {
|
|
64
|
+
collectionPointer: { table: "collection", id: tasksId },
|
|
65
|
+
collectionSchema: {
|
|
66
|
+
propertiesById: {
|
|
67
|
+
"estimate-id": { name: "Estimate", type: "number" },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
propertyIdsByKey: { estimate: "estimate-id" },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
In the embed example, return `{ status: "success", manifest }` from `getPersistedManifest` and
|
|
77
|
+
replace `initialState.dataSources` with `taskBindings`. Here, `tasks` and `estimate` are semantic
|
|
78
|
+
keys; `tasksId` and `estimate-id` identify the bound data source and property.
|
|
79
|
+
|
|
80
|
+
### Return query results
|
|
81
|
+
|
|
82
|
+
Add this handler and use it as the embed example's `handlers.queryDataSource`. Keep its existing
|
|
83
|
+
`resize` handler.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
let estimate = 3
|
|
87
|
+
|
|
88
|
+
const handlers: CustomBlockHostHandlers = {
|
|
89
|
+
queryDataSource: message => {
|
|
90
|
+
if (message.dataSourceId !== tasksId) {
|
|
91
|
+
return {
|
|
92
|
+
status: "error",
|
|
93
|
+
error: {
|
|
94
|
+
code: "unmapped_data_source",
|
|
95
|
+
message: "This data source is not available.",
|
|
96
|
+
isRetryable: false,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (message.filter || message.sorts?.length) {
|
|
102
|
+
return {
|
|
103
|
+
status: "error",
|
|
104
|
+
error: {
|
|
105
|
+
code: "invalid_data_source_query",
|
|
106
|
+
message: "This demo supports only unfiltered, unsorted queries.",
|
|
107
|
+
isRetryable: false,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const items: NotionPageData[] = [
|
|
113
|
+
{
|
|
114
|
+
object: "page",
|
|
115
|
+
id: "55555555-5555-4555-8555-555555555555",
|
|
116
|
+
parent: { type: "data_source_id", data_source_id: tasksId },
|
|
117
|
+
is_archived: false,
|
|
118
|
+
in_trash: false,
|
|
119
|
+
properties: {
|
|
120
|
+
Estimate: { id: "estimate-id", type: "number", number: estimate },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
status: "success",
|
|
127
|
+
items: items.slice(0, message.limit),
|
|
128
|
+
hasMore: items.length > message.limit,
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The SDK resolves `tasks` to `tasksId` before the host receives the request. Result properties use
|
|
135
|
+
display names as keys (`Estimate`) and include their raw property IDs (`estimate-id`). The runtime
|
|
136
|
+
handles subscription IDs, validates results, and converts them for supported older SDKs.
|
|
137
|
+
|
|
138
|
+
This demo rejects filters and sorts rather than silently ignoring them. A production adapter must
|
|
139
|
+
enforce record access, execute the requested filter and sorts, and honor the result limit. See
|
|
140
|
+
[query semantics](bridge/protocol.md#data-sources).
|
|
141
|
+
|
|
142
|
+
## Refresh active queries
|
|
143
|
+
|
|
144
|
+
After creating the host, update the in-memory value and notify active subscriptions:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
estimate = 5
|
|
148
|
+
host.refreshQuery({ dataSourceId: tasksId })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If the block has an active query for Tasks, its next result contains an estimate of `5`. A query
|
|
152
|
+
started later also reads the updated value. The application triggers refreshes when its underlying
|
|
153
|
+
data changes; the runtime re-runs matching subscriptions. Call `host.stop()` when removing the
|
|
154
|
+
embed.
|
|
155
|
+
|
|
156
|
+
`refreshQuery()` re-runs active subscriptions for the supplied data-source ID with their existing
|
|
157
|
+
filter, sorts, and limit. Overlapping refreshes queue one follow-up query. The running query
|
|
158
|
+
delivers its result first. Unsubscribe, replacement, and stop discard queued work and suppress late
|
|
159
|
+
results. Non-retryable query errors end subscriptions. Retryable errors retain them.
|
|
160
|
+
|
|
161
|
+
## Page and user operations
|
|
162
|
+
|
|
163
|
+
`createPage`, `getPage`, and `updatePage` connect page requests to your application's data access
|
|
164
|
+
and persistence. `getUser` and `listUsers` supply user data. Each handler returns a success payload
|
|
165
|
+
or an error. The runtime adds the request's correlation ID and sends the response. The application
|
|
166
|
+
enforces record access before reading or writing data.
|
|
167
|
+
|
|
168
|
+
`openPage` handles navigation to a Notion page. `resize` receives the block's requested height in
|
|
169
|
+
CSS pixels. The application owns navigation and iframe sizing.
|
|
170
|
+
|
|
171
|
+
## External navigation
|
|
172
|
+
|
|
173
|
+
`openExternalUrl` receives an `AbortSignal` and an absolute HTTP or HTTPS URL without credentials.
|
|
174
|
+
The URL is limited to 2,048 JavaScript code units before and after normalization. The adapter
|
|
175
|
+
displays the exact URL, obtains approval, and navigates only after approval. It may apply further
|
|
176
|
+
restrictions. Dismiss pending confirmation UI when the signal aborts.
|
|
177
|
+
|
|
178
|
+
Each iframe permits one pending navigation. A different request receives `pending_navigation`. A
|
|
179
|
+
duplicate of the active request is ignored. Completed requests are forgotten, so later requests
|
|
180
|
+
require fresh approval. The runtime redacts the URL from `onLog` payloads and suppresses results
|
|
181
|
+
after stop.
|
|
182
|
+
|
|
183
|
+
## Property binding
|
|
184
|
+
|
|
185
|
+
`getAutoBoundPropertyIdsByKey({ manifestProperties, propertySchemasById })` requires matching
|
|
186
|
+
property types and either an exact raw ID match or a display-name match after trimming and
|
|
187
|
+
lowercasing. IDs are not trimmed or case-folded. Exactly one candidate creates a binding. Zero or
|
|
188
|
+
multiple candidates leave the property unmapped. The result maps manifest property keys to database
|
|
189
|
+
property IDs. An ID match has no priority over a name match: if they identify different compatible
|
|
190
|
+
properties, the manifest property stays unmapped.
|
|
191
|
+
|
|
192
|
+
## Date conversion and types
|
|
193
|
+
|
|
194
|
+
`toNotionDateValue()` converts `NotionDateInput` into the four `CustomBlockDateValue` variants,
|
|
195
|
+
including reminders. Invalid input throws `InvalidNotionDateError`.
|
|
196
|
+
|
|
197
|
+
The root exports host configuration, handlers, results, and `*MessageInput` types. Wire schemas,
|
|
198
|
+
message and page types, and constants such as `LATEST_BRIDGE_PROTOCOL_VERSION` use the
|
|
199
|
+
[protocol subpath](../README.md#protocol-schemas-and-types).
|