@notionhq/custom-blocks 0.0.77 → 0.0.79

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 (100) hide show
  1. package/HOST.md +68 -34
  2. package/README.md +1 -1
  3. package/bin/src/bridge/manifest.js +3 -3
  4. package/dist/bridge/SandboxBridge.d.ts +8 -13
  5. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  6. package/dist/bridge/SandboxBridge.js +110 -39
  7. package/dist/bridge/dataSources/resolve.d.ts +3 -4
  8. package/dist/bridge/dataSources/resolve.d.ts.map +1 -1
  9. package/dist/bridge/dataSources/resolve.js +2 -12
  10. package/dist/bridge/hostState.d.ts +2 -0
  11. package/dist/bridge/hostState.d.ts.map +1 -1
  12. package/dist/bridge/loadManifest.d.ts +4 -4
  13. package/dist/bridge/loadManifest.d.ts.map +1 -1
  14. package/dist/bridge/loadManifest.js +2 -2
  15. package/dist/bridge/manifest.d.ts +3 -3
  16. package/dist/bridge/manifest.js +3 -3
  17. package/dist/bridge/messages/{ready.d.ts → connect.d.ts} +14 -12
  18. package/dist/bridge/messages/connect.d.ts.map +1 -0
  19. package/dist/bridge/messages/connect.js +38 -0
  20. package/dist/bridge/messages/hostToSandbox.d.ts +21 -0
  21. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  22. package/dist/bridge/messages/init.d.ts +35 -6
  23. package/dist/bridge/messages/init.d.ts.map +1 -1
  24. package/dist/bridge/messages/init.js +8 -4
  25. package/dist/bridge/messages/initResult.d.ts +29 -0
  26. package/dist/bridge/messages/initResult.d.ts.map +1 -0
  27. package/dist/bridge/messages/initResult.js +25 -0
  28. package/dist/bridge/messages/sandboxToHost.d.ts +21 -6
  29. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  30. package/dist/bridge/messages/sandboxToHost.js +4 -2
  31. package/dist/bridge/sandboxClient.d.ts +2 -4
  32. package/dist/bridge/sandboxClient.d.ts.map +1 -1
  33. package/dist/bridge/sandboxClient.js +2 -5
  34. package/dist/customBlock.d.ts +1 -1
  35. package/dist/customBlock.d.ts.map +1 -1
  36. package/dist/customBlock.js +1 -1
  37. package/dist/host/createCustomBlockHost.d.ts +7 -4
  38. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  39. package/dist/host/createCustomBlockHost.js +215 -38
  40. package/dist/host/lifecycle/initErrors.d.ts +1 -1
  41. package/dist/host/lifecycle/initErrors.d.ts.map +1 -1
  42. package/dist/host/lifecycle/initErrors.js +25 -7
  43. package/dist/host/lifecycle/{ready.d.ts → protocolVersion.d.ts} +1 -1
  44. package/dist/host/lifecycle/protocolVersion.d.ts.map +1 -0
  45. package/dist/host/lifecycle/types.d.ts +6 -4
  46. package/dist/host/lifecycle/types.d.ts.map +1 -1
  47. package/dist/host/messages/invalidSandboxMessage.js +0 -3
  48. package/dist/host.d.ts +5 -3
  49. package/dist/host.d.ts.map +1 -1
  50. package/dist/host.js +2 -1
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/init.d.ts +7 -7
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/init.js +70 -50
  57. package/dist/react/index.d.ts +1 -1
  58. package/dist/react/index.d.ts.map +1 -1
  59. package/dist/react/standalonePreview.d.ts.map +1 -1
  60. package/dist/react/standalonePreview.js +2 -0
  61. package/dist/react/useCustomBlockInit.d.ts +5 -6
  62. package/dist/react/useCustomBlockInit.d.ts.map +1 -1
  63. package/dist/react/useCustomBlockInit.js +3 -19
  64. package/dist/react/useRuntimeState.d.ts +4 -3
  65. package/dist/react/useRuntimeState.d.ts.map +1 -1
  66. package/dist/react/useRuntimeState.js +3 -2
  67. package/dist/version.js +1 -1
  68. package/docs/data-sources.md +10 -8
  69. package/docs/errors.md +5 -1
  70. package/docs/lifecycle.md +19 -31
  71. package/docs/manifest.md +1 -1
  72. package/package.json +1 -1
  73. package/src/bridge/SandboxBridge.ts +138 -44
  74. package/src/bridge/dataSources/resolve.ts +3 -14
  75. package/src/bridge/hostState.ts +2 -0
  76. package/src/bridge/loadManifest.ts +6 -6
  77. package/src/bridge/manifest.ts +3 -3
  78. package/src/bridge/messages/connect.ts +42 -0
  79. package/src/bridge/messages/init.ts +24 -10
  80. package/src/bridge/messages/initResult.ts +37 -0
  81. package/src/bridge/messages/sandboxToHost.ts +4 -2
  82. package/src/bridge/sandboxClient.ts +3 -8
  83. package/src/customBlock.ts +2 -2
  84. package/src/host/createCustomBlockHost.ts +265 -45
  85. package/src/host/lifecycle/initErrors.ts +25 -7
  86. package/src/host/lifecycle/types.ts +13 -4
  87. package/src/host/messages/invalidSandboxMessage.ts +0 -3
  88. package/src/host.ts +13 -2
  89. package/src/index.ts +3 -1
  90. package/src/init.ts +73 -60
  91. package/src/react/index.ts +0 -1
  92. package/src/react/standalonePreview.ts +2 -0
  93. package/src/react/useCustomBlockInit.ts +6 -27
  94. package/src/react/useRuntimeState.ts +4 -3
  95. package/dist/bridge/messages/ready.d.ts.map +0 -1
  96. package/dist/bridge/messages/ready.js +0 -43
  97. package/dist/host/lifecycle/ready.d.ts.map +0 -1
  98. package/src/bridge/messages/ready.ts +0 -48
  99. /package/dist/host/lifecycle/{ready.js → protocolVersion.js} +0 -0
  100. /package/src/host/lifecycle/{ready.ts → protocolVersion.ts} +0 -0
package/HOST.md CHANGED
@@ -18,65 +18,98 @@ import {
18
18
 
19
19
  `readIncomingType()` is intentionally best-effort. Use it after validation fails so the host can avoid NACK loops and include a useful reason in `invalidSandboxMessage`.
20
20
 
21
- `createCustomBlockHost()` is an optional protocol loop for lightweight hosts. It owns iframe message listener setup, `ready` / `init`, protocol version checks, malformed-message NACKs, request routing, auto-resize callbacks, state-change messages, cleanup, and latest-query refresh bookkeeping. It deliberately does not know about any host's data model, permissions, iframe policy, analytics, or UI state.
21
+ `createCustomBlockHost()` is an optional protocol loop for lightweight hosts. It owns iframe message listener setup, `connect` / `init` / `initResult`, protocol version checks, malformed-message NACKs, request routing, auto-resize callbacks, state-change messages, cleanup, and latest-query refresh bookkeeping. It deliberately does not know about any host's data model, permissions, iframe policy, analytics, or UI state.
22
22
 
23
23
  ```ts
24
24
  import { createCustomBlockHost } from "@notionhq/custom-blocks/host";
25
25
 
26
26
  const host = createCustomBlockHost({
27
27
  iframe,
28
+ minBridgeProtocolVersion: 3,
28
29
  initialState: {
29
30
  theme,
30
31
  blockId,
31
32
  parent,
32
33
  page,
33
34
  currentUser,
35
+ manifest: persistedManifest,
34
36
  dataSources: { bindings },
35
37
  },
36
38
  handlers: {
37
- queryDataSource: async (message) => ({ status: "success", items: [], hasMore: false }),
39
+ queryDataSource: async (message) => ({
40
+ status: "success",
41
+ items: [],
42
+ hasMore: false,
43
+ }),
38
44
  },
39
45
  });
40
46
  ```
41
47
 
42
48
  Use it when the generic protocol loop is helpful. Hosts with more specialized runtime needs can keep using the schemas and message types directly.
43
49
 
44
- Related types: `CustomBlockHostOptions`, `CustomBlockHostInitialState`, `CustomBlockHostInitialDataSources`, `CustomBlockHostDataSourcesPayload`, `CustomBlockHostHandlers`, `CustomBlockHostHandle`, `CustomBlockHostLogDirection`, `CustomBlockHostQueryDataSourceResult`, `CustomBlockHostCreatePageResult`, `CustomBlockHostGetPageResult`, `CustomBlockHostUpdatePageResult`, `CustomBlockHostGetUserResult`, and `CustomBlockHostListUsersResult`.
50
+ Responses return a `status`-discriminated result: `"success"` carries `items` and required `hasMore`; `"error"` carries the structured `error`. Set `hasMore` to `false` when no additional results are available.
51
+
52
+ Related exports: `CustomBlockHostOptions`, `CustomBlockHostInitialState`, `CustomBlockHostInitialDataSources`, `CustomBlockHostDataSourcesPayload`, `ConnectSuccessMessage`, `CustomBlockHostHandlers`, `CustomBlockHostHandle`, `CustomBlockHostLogDirection`, `CustomBlockHostQueryDataSourceResult`, `CustomBlockHostCreatePageResult`, `CustomBlockHostGetPageResult`, `CustomBlockHostUpdatePageResult`, `CustomBlockHostGetUserResult`, and `CustomBlockHostListUsersResult`.
45
53
 
46
54
  ## Bridge protocol
47
55
 
48
56
  ### Lifecycle
49
57
 
50
- The bridge is established with a handshake: the sandbox posts a `ready` when `initCustomBlock()` starts, and the host validates it and replies exactly once with `init`. Hosts should log and ignore duplicate `ready` messages instead of re-running the handshake. After that, narrower messages stream live state updates.
58
+ Initialization has three steps. A sandbox-generated `initializationId` identifies the handshake from start to finish. Ignore duplicate messages and messages carrying any other ID.
51
59
 
52
- The sandbox starts by sending `ready`. It declares the bridge protocol version, includes the custom blocks package semver as `sdkVersion` when available, and tells the host whether manifest discovery produced a usable manifest. Hosts should reject init when `ready` reports a manifest error, forwarding the sandbox's full error payload unchanged:
60
+ #### 1. Sandbox sends `connect`
53
61
 
54
- ```ts
55
- // sandbox → host
56
- { type: "ready", status: "success", bridgeProtocolVersion, sdkVersion, manifest: { /* custom_blocks.json */ } }
62
+ `connect` declares the bridge protocol version and SDK package version. A successful message may also include the manifest discovered in the sandbox:
57
63
 
58
- // sandbox → host, no manifest / no declared data requirements
59
- { type: "ready", status: "success", bridgeProtocolVersion, sdkVersion, manifest: null }
64
+ ```ts
65
+ // sandbox host without manifest
66
+ { type: "connect", initializationId, status: "success", bridgeProtocolVersion, sdkVersion }
60
67
 
61
- // sandbox → host, manifest failed to load or validate
62
- { type: "ready", status: "error", bridgeProtocolVersion, sdkVersion, error: { code: "manifest_invalid", message: "...", isRetryable: false } }
68
+ // sandbox → host with explicit manifest
69
+ { type: "connect", initializationId, status: "success", bridgeProtocolVersion, sdkVersion, manifest: { /* ... */ } }
63
70
  ```
64
71
 
65
- The host replies with exactly one `init`, which has two statuses. On success it carries the full initial state — `theme`, `blockId`, `parent`, `page`, `currentUser` (the `NotionUser` shape from the user result messages), and `dataSources: { bindings }`:
72
+ If `connect.status` is `"error"`, reply with `init.error` using the same `initializationId` and forward its complete error payload unchanged.
73
+
74
+ #### 2. Host sends `init`
75
+
76
+ The host sends exactly one `init` containing its authoritative manifest. It may
77
+ use the manifest from `connect` as input, but the manifest it returns may differ;
78
+ the sandbox applies the host's version.
79
+
80
+ `init.success` carries the chosen manifest together with the initial theme, block context, current user, and data-source bindings:
66
81
 
67
82
  ```ts
68
83
  // host → sandbox
69
- { type: "init", status: "success", theme, blockId, parent, page: { id, parent }, currentUser, dataSources: { bindings } }
84
+ { type: "init", initializationId, status: "success", theme, blockId, parent, page: { id, parent }, currentUser, manifest, dataSources: { bindings } }
70
85
  ```
71
86
 
72
- On failure it carries `error: { code, message, isRetryable }` (`CustomBlockInitErrorInfo` / `customBlockInitErrorInfoSchema`, with `code` drawn from `CustomBlockInitErrorCode` / `customBlockInitErrorCodeSchema`). The SDK surfaces this as a `CustomBlockInitError`:
87
+ If the host cannot initialize the block, send `init.error` instead:
73
88
 
74
89
  ```ts
75
90
  // host → sandbox
76
- { type: "init", status: "error", error: { code: "context_unavailable", message: "...", isRetryable: true } }
91
+ { type: "init", initializationId, status: "error", error: { code: "context_unavailable", message: "...", isRetryable: true } }
77
92
  ```
78
93
 
79
- After `init`, narrower messages update live state without re-running the handshake `themeChanged`, `parentChanged`, `pageChanged`, `currentUserChanged`, and `dataSourcesChanged`. Each replaces just its slice of state:
94
+ Use `CustomBlockInitErrorCode` / `CustomBlockInitErrorInfo` to construct this error and `customBlockInitErrorCodeSchema` / `customBlockInitErrorInfoSchema` to validate it. `init.error` is terminal; the sandbox does not reply with `initResult`.
95
+
96
+ #### 3. Sandbox sends `initResult`
97
+
98
+ After applying `init.success`, the sandbox acknowledges whether it accepted the manifest and bindings:
99
+
100
+ ```ts
101
+ // sandbox → host
102
+ { type: "initResult", initializationId, status: "success" }
103
+
104
+ // sandbox → host, host supplied bindings the SDK could not apply
105
+ { type: "initResult", initializationId, status: "error", error: { code: "invalid_init_bindings", message: "...", isRetryable: false } }
106
+ ```
107
+
108
+ Use `CustomBlockInitResultErrorCode` / `CustomBlockInitResultErrorInfo` to construct this error and `customBlockInitResultErrorCodeSchema` / `customBlockInitResultErrorInfoSchema` to validate it.
109
+
110
+ Wait for `initResult.success` before uncovering the iframe or sending live updates. `initResult.error` is terminal.
111
+
112
+ After `initResult.success`, narrower messages update live state without re-running the handshake — `themeChanged`, `parentChanged`, `pageChanged`, `currentUserChanged`, and `dataSourcesChanged`. Each replaces just its slice of state:
80
113
 
81
114
  ```ts
82
115
  // host → sandbox, any time after init
@@ -88,7 +121,7 @@ After `init`, narrower messages update live state without re-running the handsha
88
121
 
89
122
  ### Versioning
90
123
 
91
- `ready` includes `bridgeProtocolVersion` (the host <-> sandbox version) and `sdkVersion` (the package semver). Hosts should reject or fail `init` when the sandbox reports a `bridgeProtocolVersion` the host does not implement, but should treat `sdkVersion` as purely analytics metadata.
124
+ `connect` includes `bridgeProtocolVersion` (the host <-> sandbox version) and `sdkVersion` (the package semver). Hosts should reject or fail `init` when the sandbox reports a `bridgeProtocolVersion` the host does not implement, but should treat `sdkVersion` as purely analytics metadata.
92
125
 
93
126
  If the sandbox reports a version below the host's supported minimum, reply with `init.status: "error"` and `error.code: "unsupported_protocol_version"`. If the reported version is structurally invalid (not a positive integer — e.g. `0`, negative, fractional, `NaN`, or `Infinity`), reply with `error.code: "invalid_protocol_version"` instead.
94
127
 
@@ -142,9 +175,10 @@ Messages sent from the sandbox to the host. Parse `window` `message` events with
142
175
 
143
176
  | Wire type | Type / schema | Behavior |
144
177
  | -------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
- | `ready` | `ReadyMessage` / `readyMessageSchema` | One-shot handshake; carries `bridgeProtocolVersion`, `sdkVersion`, and the manifest. |
178
+ | `connect` | `ConnectMessage` / `connectMessageSchema` | Starts the one-shot handshake with a sandbox-generated `initializationId`; carries `bridgeProtocolVersion`, `sdkVersion`, and the manifest. |
179
+ | `initResult` | `InitResultMessage` / `initResultMessageSchema` | Echoes the handshake `initializationId` and reports whether the sandbox applied `init.success`. |
146
180
  | `queryDataSource` | `QueryDataSourceMessage` / `queryDataSourceMessageSchema` | Starts or updates a `subscriptionId`-keyed subscription for rows in a raw `dataSourceId`. Later results for the same subscription replace its current value. |
147
- | `createPage` | `CreatePageMessage` / `createPageMessageSchema` | `requestId`-keyed page creation with properties and optional position; parent is `page_id` or `data_source_id`. Create-time icon and cover are not supported. |
181
+ | `createPage` | `CreatePageMessage` / `createPageMessageSchema` | `requestId`-keyed page creation with properties and optional position; parent is `page_id` or `data_source_id`. Create-time icon and cover are not supported. |
148
182
  | `getPage` | `GetPageMessage` / `getPageMessageSchema` | `requestId`-keyed page fetch by page id. |
149
183
  | `updatePage` | `UpdatePageMessage` / `updatePageMessageSchema` | `requestId`-keyed patch (properties, icon, cover, or `archived`). |
150
184
  | `getUser` | `GetUserMessage` / `getUserMessageSchema` | `requestId`-keyed user fetch by user id. |
@@ -158,20 +192,20 @@ Messages sent from the sandbox to the host. Parse `window` `message` events with
158
192
 
159
193
  Messages sent from the host to the sandbox. Same `{ wire type, type / schema, behavior }` shape; the type / schema column points to the payload shape.
160
194
 
161
- | Wire type | Type / schema | Behavior |
162
- | ----------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
163
- | `init` | `InitMessage` / `initMessageSchema` | Sent exactly once in response to `ready`; carries the success/error payload described under [Lifecycle](#lifecycle). |
164
- | `themeChanged` | `ThemeChangedMessage` / `themeChangedMessageSchema` | Replaces the current theme. |
165
- | `parentChanged` | `ParentChangedMessage` / `parentChangedMessageSchema` | Replaces the custom block parent without disturbing theme, block ID, page ID, user, or query state. |
166
- | `pageChanged` | `PageChangedMessage` / `pageChangedMessageSchema` | Replaces the nearest page ancestor without disturbing theme, block ID, parent, user, or query state. |
167
- | `currentUserChanged` | `CurrentUserChangedMessage` / `currentUserChangedMessageSchema` | Replaces the current viewer record. Send when any viewer field changes (name, avatar, email). |
168
- | `dataSourcesChanged` | `DataSourcesChangedMessage` / `dataSourcesChangedMessageSchema` | Replaces data-source bindings; the sandbox preserves cached query state for keys that still exist and drops removed keys. |
169
- | `queryDataSourceResult` | `QueryDataSourceResultMessage` / `queryDataSourceResultMessageSchema` | `subscriptionId`-keyed update with `status: "success"`, `items`, and `hasMore`, or `status: "error"` and `error`. |
170
- | `createPageResult` | `CreatePageResultMessage` / `createPageResultMessageSchema` | `requestId`-keyed page response with `status: "success"` or `status: "error"`. |
171
- | `getPageResult` | `GetPageResultMessage` / `getPageResultMessageSchema` | Same success/error shape as `createPageResult`. |
172
- | `updatePageResult` | `UpdatePageResultMessage` / `updatePageResultMessageSchema` | Same success/error shape as `createPageResult`. |
173
- | `getUserResult` | `GetUserResultMessage` / `getUserResultMessageSchema` | `requestId`-keyed user response with `status: "success"` or `status: "error"`. |
174
- | `listUsersResult` | `ListUsersResultMessage` / `listUsersResultMessageSchema` | Same success/error shape as `getUserResult`. |
195
+ | Wire type | Type / schema | Behavior |
196
+ | ----------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
197
+ | `init` | `InitMessage` / `initMessageSchema` | Sent exactly once in response to `connect`; echoes its `initializationId` and carries the success/error payload described under [Lifecycle](#lifecycle). |
198
+ | `themeChanged` | `ThemeChangedMessage` / `themeChangedMessageSchema` | Replaces the current theme. |
199
+ | `parentChanged` | `ParentChangedMessage` / `parentChangedMessageSchema` | Replaces the custom block parent without disturbing theme, block ID, page ID, user, or query state. |
200
+ | `pageChanged` | `PageChangedMessage` / `pageChangedMessageSchema` | Replaces the nearest page ancestor without disturbing theme, block ID, parent, user, or query state. |
201
+ | `currentUserChanged` | `CurrentUserChangedMessage` / `currentUserChangedMessageSchema` | Replaces the current viewer record. Send when any viewer field changes (name, avatar, email). |
202
+ | `dataSourcesChanged` | `DataSourcesChangedMessage` / `dataSourcesChangedMessageSchema` | Replaces data-source bindings; the sandbox preserves cached query state for keys that still exist and drops removed keys. |
203
+ | `queryDataSourceResult` | `QueryDataSourceResultMessage` / `queryDataSourceResultMessageSchema` | `subscriptionId`-keyed update with `status: "success"`, `items`, and `hasMore`, or `status: "error"` and `error`. |
204
+ | `createPageResult` | `CreatePageResultMessage` / `createPageResultMessageSchema` | `requestId`-keyed page response with `status: "success"` or `status: "error"`. |
205
+ | `getPageResult` | `GetPageResultMessage` / `getPageResultMessageSchema` | Same success/error shape as `createPageResult`. |
206
+ | `updatePageResult` | `UpdatePageResultMessage` / `updatePageResultMessageSchema` | Same success/error shape as `createPageResult`. |
207
+ | `getUserResult` | `GetUserResultMessage` / `getUserResultMessageSchema` | `requestId`-keyed user response with `status: "success"` or `status: "error"`. |
208
+ | `listUsersResult` | `ListUsersResultMessage` / `listUsersResultMessageSchema` | Same success/error shape as `getUserResult`. |
175
209
  | `invalidSandboxMessage` | `InvalidSandboxMessage` / `invalidSandboxMessageSchema` | Host-side NACK for a sandbox message it could not parse. |
176
210
 
177
211
  `HostToSandboxMessage` / `hostToSandboxMessageSchema` is the discriminated union over all of the above.
package/README.md CHANGED
@@ -35,7 +35,7 @@ export function App() {
35
35
  }
36
36
  ```
37
37
 
38
- `<NotionCustomBlock>` runs the SDK ↔ host handshake (sends `ready` with the manifest, awaits `init`) and only mounts `children` once it resolves. Inside the wrapper, every hook returns non-nullable values — there's no separate gating component to write. It also runs `useCustomBlockAutoResize` for you by default; pass `autoResize={false}` to opt out.
38
+ `<NotionCustomBlock>` runs the SDK ↔ host handshake (`connect` `init` `initResult`) and only mounts `children` once it resolves. Inside the wrapper, every hook returns non-nullable values — there's no separate gating component to write. It also runs `useCustomBlockAutoResize` for you by default; pass `autoResize={false}` to opt out.
39
39
 
40
40
  ## Reference
41
41
 
@@ -2,9 +2,9 @@ import * as v from "valibot";
2
2
  import { notionPropertyTypeSchema } from "./dataSources/propertySchema.js";
3
3
  /**
4
4
  * User-authored manifest declaring the data sources the custom block expects.
5
- * Lives at `custom_blocks.json` in the project root and is forwarded to the host with
6
- * the bridge `ready` message so the host can pre-bind data sources, surface
7
- * configuration UI, etc. The `notionCustomBlock` Vite plugin from
5
+ * Lives at `custom_blocks.json` in the project root. The sandbox may send it in
6
+ * `connect`, and the host returns the authoritative manifest in `init`. The
7
+ * `notionCustomBlock` Vite plugin from
8
8
  * `@notionhq/custom-blocks/vite` wires the JSON file into the dev server and
9
9
  * the build output.
10
10
  */
@@ -3,14 +3,13 @@ import type { NotionDataSource } from "./dataSources/dataSource.js";
3
3
  import type { NotionDataSourcePageUpdateInput, NotionDataSourcePageUpdateResult } from "./dataSources/dataSourcePage.js";
4
4
  import { type CustomBlockHostState } from "./hostState.js";
5
5
  import type { ManifestLoadResult } from "./loadManifest.js";
6
- import type { CustomBlockManifest } from "./manifest.js";
7
6
  import { type InitMessage } from "./messages/init.js";
8
7
  /**
9
8
  * Used to ensure that the host and client are using the same version of the bridge protocol. A
10
9
  * single host needs to support multiple custom blocks built with different versions of the bridge
11
10
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
12
11
  */
13
- export declare const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 2;
12
+ export declare const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 3;
14
13
  /**
15
14
  * A single entry in the bridge message log. Kept intentionally plain so the log
16
15
  * is copy-pasteable to a local coding agent without needing extra context.
@@ -25,36 +24,32 @@ export declare class SandboxBridge {
25
24
  private listeners;
26
25
  private messageLog;
27
26
  private messageLogListeners;
27
+ private nextRequestId;
28
28
  private readonly pendingCreatePage;
29
29
  private readonly pendingGetPage;
30
30
  private readonly pendingGetUser;
31
31
  private readonly pendingListUsers;
32
32
  private readonly pendingUpdatePage;
33
- private hasSentReady;
33
+ private hasSentConnect;
34
+ private hasReceivedInit;
35
+ private initializationId;
34
36
  private latestDataSourceBindings;
37
+ private isMockState;
35
38
  private resolveInit;
36
39
  private rejectInit;
37
40
  private readonly initMessage;
38
- private manifest;
39
41
  constructor();
40
42
  private static MAX_LOG_ENTRIES;
41
43
  private logMessage;
42
44
  getMessageLog(): readonly MessageLogEntry[];
43
45
  subscribeToMessageLog(listener: () => void): () => boolean;
44
- awaitInit(signal?: AbortSignal): Promise<InitMessage>;
45
- sendReady(manifestResult: ManifestLoadResult): void;
46
+ awaitInit(signal?: AbortSignal): Promise<void>;
47
+ sendConnect(manifestResult: ManifestLoadResult): void;
46
48
  private postToHost;
47
49
  private notify;
48
50
  private handleMessage;
49
51
  subscribe(listener: () => void): () => boolean;
50
52
  getHostState(): CustomBlockHostState;
51
- /**
52
- * The author-declared manifest loaded from `custom_blocks.json` and forwarded
53
- * to the host in `ready`. `null` when it failed to load/parse and the host
54
- * should reject init via `ready.status: "error"`. Static for the lifetime
55
- * of the sandbox.
56
- */
57
- getManifest(): CustomBlockManifest | null;
58
53
  /**
59
54
  * Apply an `init` payload as if it had arrived from the host. Lets callers
60
55
  * seed the bridge directly (e.g. the React provider's standalone preview
@@ -1 +1 @@
1
- {"version":3,"file":"SandboxBridge.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/SandboxBridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SandboxBridge.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/SandboxBridge.ts"],"names":[],"mappings":"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"}
@@ -6,14 +6,14 @@ import { resolvePropertyWriteMapForDataSource } from "./dataSources/resolvePrope
6
6
  import { createEmptyDataSourceQueryState, } from "./hostState.js";
7
7
  import { readIncomingType } from "./incomingType.js";
8
8
  import { hostToSandboxMessageSchema } from "./messages/hostToSandbox.js";
9
- import { CustomBlockInitError } from "./messages/init.js";
9
+ import { CustomBlockInitializationError, initMessageSchema, } from "./messages/init.js";
10
10
  import { PendingRequests } from "./pendingRequests.js";
11
11
  /**
12
12
  * Used to ensure that the host and client are using the same version of the bridge protocol. A
13
13
  * single host needs to support multiple custom blocks built with different versions of the bridge
14
14
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
15
15
  */
16
- export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 2;
16
+ export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 3;
17
17
  export class SandboxBridge {
18
18
  constructor() {
19
19
  this.hostState = {
@@ -23,18 +23,20 @@ export class SandboxBridge {
23
23
  this.listeners = new Set();
24
24
  this.messageLog = [];
25
25
  this.messageLogListeners = new Set();
26
+ this.nextRequestId = 1;
26
27
  this.pendingCreatePage = new PendingRequests("custom-block-create-page");
27
28
  this.pendingGetPage = new PendingRequests("custom-block-get-page");
28
29
  this.pendingGetUser = new PendingRequests("custom-block-get-user");
29
30
  this.pendingListUsers = new PendingRequests("custom-block-list-users");
30
31
  this.pendingUpdatePage = new PendingRequests("custom-block-update-page");
31
- this.hasSentReady = false;
32
+ this.hasSentConnect = false;
33
+ this.hasReceivedInit = false;
32
34
  this.latestDataSourceBindings = {};
35
+ this.isMockState = false;
33
36
  this.initMessage = new Promise((resolve, reject) => {
34
37
  this.resolveInit = resolve;
35
38
  this.rejectInit = reject;
36
39
  });
37
- this.manifest = null;
38
40
  this.notify = () => {
39
41
  for (const listener of this.listeners) {
40
42
  listener();
@@ -79,7 +81,16 @@ export class SandboxBridge {
79
81
  // `init` is the only message valid before initialization. Handle it up
80
82
  // front so every later case can assume `status === "initialized"`.
81
83
  if (message.type === "init") {
82
- this.applyInit(message);
84
+ if (message.initializationId !== this.initializationId) {
85
+ console.warn(`[custom-blocks-sdk] ignoring init for unknown initializationId ${message.initializationId}`);
86
+ return;
87
+ }
88
+ if (this.hasReceivedInit) {
89
+ console.warn("[custom-blocks-sdk] ignoring duplicate init message");
90
+ return;
91
+ }
92
+ this.hasReceivedInit = true;
93
+ this.applyInit(message, true);
83
94
  return;
84
95
  }
85
96
  // Alias to keep TS's narrowed `InitializedHostState` across the switch
@@ -127,10 +138,12 @@ export class SandboxBridge {
127
138
  const dataSources = reuseDataSourcesForUnchangedBindings({
128
139
  previousDataSources: hostState.dataSources,
129
140
  previousBindings: this.latestDataSourceBindings,
130
- nextDataSources: resolveDataSources({
131
- manifest: this.manifest,
132
- dataSourceBindings: nextBindings,
133
- }),
141
+ nextDataSources: this.isMockState
142
+ ? resolveMockDataSources(nextBindings)
143
+ : resolveDataSources({
144
+ manifest: hostState.manifest,
145
+ dataSourceBindings: nextBindings,
146
+ }),
134
147
  nextBindings,
135
148
  });
136
149
  this.latestDataSourceBindings = nextBindings;
@@ -234,7 +247,7 @@ export class SandboxBridge {
234
247
  }
235
248
  }
236
249
  };
237
- // `ready` is sent later by `initCustomBlock` (after the manifest fetch
250
+ // `connect` is sent later by `initCustomBlock` (after the manifest fetch
238
251
  // resolves). Top-level / no-iframe rejection is handled there too, so
239
252
  // the constructor just attaches the listener.
240
253
  if (typeof window !== "undefined") {
@@ -272,42 +285,46 @@ export class SandboxBridge {
272
285
  }
273
286
  const onAbort = () => reject(signal.reason);
274
287
  signal.addEventListener("abort", onAbort, { once: true });
275
- this.initMessage.then(message => {
288
+ this.initMessage.then(() => {
276
289
  signal.removeEventListener("abort", onAbort);
277
- resolve(message);
290
+ resolve();
278
291
  }, err => {
279
292
  signal.removeEventListener("abort", onAbort);
280
293
  reject(err);
281
294
  });
282
295
  });
283
296
  }
284
- sendReady(manifestResult) {
297
+ sendConnect(manifestResult) {
285
298
  if (typeof window === "undefined") {
286
299
  return;
287
300
  }
288
- if (this.hasSentReady) {
289
- console.warn("[custom-blocks-sdk] ignoring duplicate ready message");
301
+ if (this.hasSentConnect) {
302
+ console.warn("[custom-blocks-sdk] ignoring duplicate connect message");
290
303
  return;
291
304
  }
292
305
  const { manifest, error } = manifestResult;
293
- this.hasSentReady = true;
294
- this.manifest = manifest;
295
- const readyMessage = error !== undefined
306
+ this.hasSentConnect = true;
307
+ const initializationId = `custom-block-initialization-${this.nextRequestId}`;
308
+ this.nextRequestId += 1;
309
+ this.initializationId = initializationId;
310
+ const connectMessage = error !== undefined
296
311
  ? {
297
- type: "ready",
312
+ type: "connect",
313
+ initializationId,
298
314
  status: "error",
299
315
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
300
316
  sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
301
317
  error,
302
318
  }
303
319
  : {
304
- type: "ready",
320
+ type: "connect",
321
+ initializationId,
305
322
  status: "success",
306
323
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
307
324
  sdkVersion: CUSTOM_BLOCKS_SDK_VERSION,
308
- manifest,
325
+ ...(manifest !== null ? { manifest } : {}),
309
326
  };
310
- this.postToHost(readyMessage);
327
+ this.postToHost(connectMessage);
311
328
  }
312
329
  postToHost(message) {
313
330
  console.debug("[custom-blocks-sdk] outbound postMessage", message);
@@ -321,15 +338,6 @@ export class SandboxBridge {
321
338
  getHostState() {
322
339
  return this.hostState;
323
340
  }
324
- /**
325
- * The author-declared manifest loaded from `custom_blocks.json` and forwarded
326
- * to the host in `ready`. `null` when it failed to load/parse and the host
327
- * should reject init via `ready.status: "error"`. Static for the lifetime
328
- * of the sandbox.
329
- */
330
- getManifest() {
331
- return this.manifest;
332
- }
333
341
  /**
334
342
  * Apply an `init` payload as if it had arrived from the host. Lets callers
335
343
  * seed the bridge directly (e.g. the React provider's standalone preview
@@ -337,9 +345,13 @@ export class SandboxBridge {
337
345
  * of why it's being seeded.
338
346
  */
339
347
  setMockState(message) {
340
- this.applyInit(message);
348
+ this.isMockState = true;
349
+ this.applyInit(v.parse(initMessageSchema, message), false);
341
350
  }
342
- applyInit(message) {
351
+ applyInit(message, postResult) {
352
+ if (postResult) {
353
+ this.isMockState = false;
354
+ }
343
355
  if (message.status === "error") {
344
356
  // The host couldn't construct block location for this block (most commonly the parent record
345
357
  // failed to resolve). Surface the failure through the `awaitInit` promise so callers see
@@ -349,7 +361,7 @@ export class SandboxBridge {
349
361
  // back over the bridge since the host has already given up.
350
362
  console.error(`[custom-blocks-sdk] host reported init error (${message.error.code}): ${message.error.message}`);
351
363
  if (this.rejectInit) {
352
- this.rejectInit(new CustomBlockInitError(message.error));
364
+ this.rejectInit(new CustomBlockInitializationError(message.error));
353
365
  this.resolveInit = undefined;
354
366
  this.rejectInit = undefined;
355
367
  }
@@ -357,10 +369,28 @@ export class SandboxBridge {
357
369
  }
358
370
  const { blockId, parent, page } = message;
359
371
  this.latestDataSourceBindings = message.dataSources.bindings;
360
- const dataSources = resolveDataSources({
361
- manifest: this.manifest,
362
- dataSourceBindings: this.latestDataSourceBindings,
363
- });
372
+ const dataSources = postResult
373
+ ? resolveDataSources({
374
+ manifest: message.manifest,
375
+ dataSourceBindings: this.latestDataSourceBindings,
376
+ })
377
+ : resolveMockDataSources(this.latestDataSourceBindings);
378
+ const bindingError = getInitBindingError(message.manifest, dataSources);
379
+ if (postResult && bindingError !== undefined) {
380
+ const result = {
381
+ type: "initResult",
382
+ initializationId: message.initializationId,
383
+ status: "error",
384
+ error: bindingError,
385
+ };
386
+ this.postToHost(result);
387
+ if (this.rejectInit) {
388
+ this.rejectInit(new CustomBlockInitializationError(bindingError));
389
+ this.resolveInit = undefined;
390
+ this.rejectInit = undefined;
391
+ }
392
+ return;
393
+ }
364
394
  this.hostState = {
365
395
  status: "initialized",
366
396
  theme: message.theme,
@@ -368,15 +398,24 @@ export class SandboxBridge {
368
398
  parent,
369
399
  page,
370
400
  currentUser: message.currentUser,
401
+ manifest: message.manifest,
371
402
  dataSources,
372
403
  dataSourceState: {},
373
404
  };
374
405
  this.notify();
406
+ if (postResult) {
407
+ const result = {
408
+ type: "initResult",
409
+ initializationId: message.initializationId,
410
+ status: "success",
411
+ };
412
+ this.postToHost(result);
413
+ }
375
414
  // Resolve the awaitInit promise once. Subsequent `init` messages
376
415
  // (the host shouldn't send these, but be tolerant) update state but
377
416
  // don't re-resolve.
378
417
  if (this.resolveInit) {
379
- this.resolveInit(message);
418
+ this.resolveInit();
380
419
  this.resolveInit = undefined;
381
420
  this.rejectInit = undefined;
382
421
  }
@@ -693,6 +732,38 @@ function makeDataSourceSubscriptionId(args) {
693
732
  const { key } = args;
694
733
  return `data-source:${encodeURIComponent(key)}`;
695
734
  }
735
+ function getInitBindingError(manifest, dataSources) {
736
+ for (const [dataSourceKey, manifestDataSource] of Object.entries(manifest.dataSources)) {
737
+ const dataSource = dataSources.find(entry => entry.key === dataSourceKey);
738
+ if (dataSource?.collectionPointer === undefined ||
739
+ dataSource.collectionSchema === undefined) {
740
+ return {
741
+ code: "invalid_init_bindings",
742
+ message: `Host did not provide a complete binding for data source "${dataSourceKey}".`,
743
+ isRetryable: false,
744
+ };
745
+ }
746
+ for (const propertyKey of Object.keys(manifestDataSource.properties ?? {})) {
747
+ if (dataSource.propertyIdsByKey[propertyKey] === undefined) {
748
+ return {
749
+ code: "invalid_init_bindings",
750
+ message: `Host did not provide a valid binding for property "${dataSourceKey}.${propertyKey}".`,
751
+ isRetryable: false,
752
+ };
753
+ }
754
+ }
755
+ }
756
+ return undefined;
757
+ }
758
+ function resolveMockDataSources(bindings) {
759
+ return Object.entries(bindings).map(([key, binding]) => ({
760
+ key,
761
+ collectionPointer: binding.collectionPointer,
762
+ collectionSchema: binding.collectionSchema,
763
+ propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
764
+ propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
765
+ }));
766
+ }
696
767
  function formatInvalidHostReason(incomingType, issues) {
697
768
  const labelled = incomingType
698
769
  ? `host message of type "${incomingType}"`
@@ -1,16 +1,15 @@
1
1
  import type { CustomBlockManifest } from "../manifest.js";
2
2
  import type { NotionDataSource, NotionDataSourceBindings } from "./dataSource.js";
3
3
  type ResolveDataSourcesArgs = {
4
- manifest: CustomBlockManifest | null;
4
+ manifest: CustomBlockManifest;
5
5
  dataSourceBindings: NotionDataSourceBindings;
6
6
  };
7
7
  /**
8
8
  * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
9
9
  *
10
10
  * Combines the host-supplied bindings (collection pointers + schemas) with the
11
- * manifest's declared data-source keys. Property mappings are host-owned: the
12
- * SDK validates the supplied property id against the manifest and live schema,
13
- * but does not infer a missing mapping.
11
+ * manifest's declared data-source keys. The host owns every mapping; the SDK
12
+ * validates supplied IDs but never infers omitted bindings.
14
13
  */
15
14
  export declare function resolveDataSources(args: ResolveDataSourcesArgs): NotionDataSource[];
16
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EACX,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,kBAAkB,EAAE,wBAAwB,CAAA;CAC5C,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,sBAAsB,GAC1B,gBAAgB,EAAE,CAyCpB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/dataSources/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EACX,gBAAgB,EAChB,wBAAwB,EACxB,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,kBAAkB,EAAE,wBAAwB,CAAA;CAC5C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,sBAAsB,GAC1B,gBAAgB,EAAE,CA+BpB"}
@@ -2,20 +2,10 @@
2
2
  * Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
3
3
  *
4
4
  * Combines the host-supplied bindings (collection pointers + schemas) with the
5
- * manifest's declared data-source keys. Property mappings are host-owned: the
6
- * SDK validates the supplied property id against the manifest and live schema,
7
- * but does not infer a missing mapping.
5
+ * manifest's declared data-source keys. The host owns every mapping; the SDK
6
+ * validates supplied IDs but never infers omitted bindings.
8
7
  */
9
8
  export function resolveDataSources(args) {
10
- if (args.manifest === null) {
11
- return Object.entries(args.dataSourceBindings).map(([key, binding]) => ({
12
- key,
13
- collectionPointer: binding.collectionPointer,
14
- collectionSchema: binding.collectionSchema,
15
- propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
16
- propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
17
- }));
18
- }
19
9
  return Object.entries(args.manifest.dataSources).map(([key, manifestDataSource]) => {
20
10
  const binding = args.dataSourceBindings[key];
21
11
  const propertySchemasById = binding?.collectionSchema?.propertiesById ?? {};
@@ -2,6 +2,7 @@ import type { NotionDataSource } from "./dataSources/dataSource.js";
2
2
  import type { NotionDataSourcePage, NotionDataSourcePageBridge, NotionDataSourcePageUpdateInput, NotionDataSourcePageUpdateResult } from "./dataSources/dataSourcePage.js";
3
3
  import type { NotionPropertySchema } from "./dataSources/propertySchema.js";
4
4
  import type { NotionBlockId } from "./ids.js";
5
+ import type { CustomBlockManifest } from "./manifest.js";
5
6
  import type { CustomBlockQueryDataSourceErrorInfo } from "./messages/queryDataSourceResult.js";
6
7
  import type { CustomBlockPage, NotionPageId } from "./pages/page.js";
7
8
  import type { NotionParent } from "./parent.js";
@@ -19,6 +20,7 @@ export type InitializedHostState = {
19
20
  parent: NotionParent;
20
21
  page: CustomBlockPage;
21
22
  currentUser: NotionUser;
23
+ manifest: CustomBlockManifest;
22
24
  dataSources: NotionDataSource[];
23
25
  dataSourceState: Record<string, DataSourceQueryState>;
24
26
  };