@notionhq/custom-blocks 0.1.42 → 0.1.44

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 (42) hide show
  1. package/README.md +3 -3
  2. package/dist/bridge/SandboxBridge.d.ts +9 -2
  3. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  4. package/dist/bridge/SandboxBridge.js +31 -13
  5. package/dist/bridge/dataSources/subscribe.d.ts +3 -0
  6. package/dist/bridge/dataSources/subscribe.d.ts.map +1 -0
  7. package/dist/bridge/dataSources/subscribe.js +63 -0
  8. package/dist/bridge/hostState.d.ts.map +1 -1
  9. package/dist/bridge/hostState.js +8 -5
  10. package/dist/bridge/notifyListener.d.ts +3 -0
  11. package/dist/bridge/notifyListener.d.ts.map +1 -0
  12. package/dist/bridge/notifyListener.js +16 -0
  13. package/dist/bridge/sandboxClient.d.ts +13 -4
  14. package/dist/bridge/sandboxClient.d.ts.map +1 -1
  15. package/dist/bridge/sandboxClient.js +23 -5
  16. package/dist/customBlock.d.ts +6 -0
  17. package/dist/customBlock.d.ts.map +1 -1
  18. package/dist/customBlock.js +6 -0
  19. package/dist/protocol/index.d.ts +1 -0
  20. package/dist/protocol/index.js +1 -0
  21. package/dist/protocol/messages/sandboxToHost.d.ts +3 -0
  22. package/dist/protocol/messages/sandboxToHost.js +2 -0
  23. package/dist/protocol/messages/unsubscribeDataSourceQuery.d.ts +10 -0
  24. package/dist/protocol/messages/unsubscribeDataSourceQuery.js +9 -0
  25. package/dist/react/useDataSource.d.ts.map +1 -1
  26. package/dist/react/useDataSource.js +26 -29
  27. package/dist/types.d.ts +29 -17
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/version.js +1 -1
  30. package/docs/data-sources.md +213 -66
  31. package/docs/lifecycle.md +1 -11
  32. package/docs/pages.md +16 -8
  33. package/docs/users.md +0 -1
  34. package/package.json +1 -1
  35. package/src/bridge/SandboxBridge.ts +49 -19
  36. package/src/bridge/dataSources/subscribe.ts +97 -0
  37. package/src/bridge/hostState.ts +9 -6
  38. package/src/bridge/notifyListener.ts +14 -0
  39. package/src/bridge/sandboxClient.ts +31 -10
  40. package/src/customBlock.ts +7 -0
  41. package/src/react/useDataSource.ts +32 -39
  42. package/src/types.ts +37 -17
@@ -32,6 +32,7 @@ import type { ListUsersMessage } from "@notionhq/custom-blocks-protocol/messages
32
32
  import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
33
33
  import type { CustomBlockQueryDataSourceErrorInfo } from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
34
34
  import type { ResizeMessage } from "@notionhq/custom-blocks-protocol/messages/resize.js"
35
+ import type { UnsubscribeDataSourceQueryMessage } from "@notionhq/custom-blocks-protocol/messages/unsubscribeDataSourceQuery.js"
35
36
  import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
36
37
  import { CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION } from "@notionhq/custom-blocks-protocol/protocolVersion.js"
37
38
  import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
@@ -40,6 +41,7 @@ import { globalPrimedResponsesCache } from "../testing.js"
40
41
  import type {
41
42
  CreatePageArgs,
42
43
  CreatePageResult,
44
+ DataSourceQueryOptions,
43
45
  GetPageResult,
44
46
  GetUserResult,
45
47
  ListUsersArgs,
@@ -48,7 +50,6 @@ import type {
48
50
  NotionUserId,
49
51
  UpdatePageArgs,
50
52
  UpdatePageResult,
51
- UseDataSourceOptions,
52
53
  } from "../types.js"
53
54
  import { unreachable } from "../utils.js"
54
55
  import { CUSTOM_BLOCKS_SDK_VERSION } from "../version.js"
@@ -65,6 +66,7 @@ import {
65
66
  type DataSourceQueryState,
66
67
  } from "./hostState.js"
67
68
  import type { ManifestLoadResult } from "./loadManifest.js"
69
+ import { notifyListener } from "./notifyListener.js"
68
70
  import { PendingRequests } from "./pendingRequests.js"
69
71
 
70
72
  /**
@@ -88,6 +90,14 @@ const RESPONSE_TYPE_BY_REQUEST = new Map([
88
90
 
89
91
  const INIT_RESULT_TIMER_FALLBACK_MS = 100
90
92
 
93
+ export type QueryDataSourceArgs = {
94
+ subscriptionId: string
95
+ key: string
96
+ options?: DataSourceQueryOptions
97
+ /** Send the query even if an identical query is already loading. */
98
+ forceRequery?: boolean
99
+ }
100
+
91
101
  export class SandboxBridge {
92
102
  private hostState: CustomBlockHostState = {
93
103
  status: "uninitialized",
@@ -281,7 +291,7 @@ export class SandboxBridge {
281
291
 
282
292
  private notify = () => {
283
293
  for (const listener of this.listeners) {
284
- listener()
294
+ notifyListener(listener)
285
295
  }
286
296
  }
287
297
 
@@ -437,15 +447,27 @@ export class SandboxBridge {
437
447
  nextBindings,
438
448
  })
439
449
  this.latestDataSourceBindings = nextBindings
440
- // Drop cached query state for subscriptions whose key no longer exists.
441
- const nextKeys = new Set(dataSources.map(s => s.key))
450
+ // Preserve rows only while the key still refers to the same backing data source.
451
+ const previousSourcesByKey = new Map(
452
+ hostState.dataSources.map(source => [source.key, source]),
453
+ )
454
+ const nextSourcesByKey = new Map(
455
+ dataSources.map(source => [source.key, source]),
456
+ )
442
457
  const prunedState: Record<string, DataSourceQueryState> = {}
443
458
  for (const [subscriptionId, state] of Object.entries(
444
459
  hostState.dataSourceState,
445
460
  )) {
446
- if (nextKeys.has(state.dataSourceKey)) {
447
- prunedState[subscriptionId] = state
461
+ const nextSource = nextSourcesByKey.get(state.dataSourceKey)
462
+ if (nextSource === undefined) {
463
+ continue
448
464
  }
465
+ const previousSource = previousSourcesByKey.get(state.dataSourceKey)
466
+ prunedState[subscriptionId] =
467
+ previousSource?.collectionPointer?.id ===
468
+ nextSource.collectionPointer?.id
469
+ ? state
470
+ : createEmptyDataSourceQueryState(state.dataSourceKey)
449
471
  }
450
472
  this.hostState = {
451
473
  ...hostState,
@@ -720,11 +742,12 @@ export class SandboxBridge {
720
742
  return `data-source:${globalThis.crypto.randomUUID()}`
721
743
  }
722
744
 
723
- queryDataSource(
724
- subscriptionId: string,
725
- key: string,
726
- options: UseDataSourceOptions = {},
727
- ) {
745
+ queryDataSource({
746
+ subscriptionId,
747
+ key,
748
+ options = {},
749
+ forceRequery = false,
750
+ }: QueryDataSourceArgs) {
728
751
  if (this.hostState.status !== "initialized") {
729
752
  return
730
753
  }
@@ -770,6 +793,7 @@ export class SandboxBridge {
770
793
  const query = resolvedQuery.query
771
794
 
772
795
  if (
796
+ !forceRequery &&
773
797
  currentState.isLoading &&
774
798
  currentState.latestQueryIdentity === query.identity
775
799
  ) {
@@ -790,8 +814,6 @@ export class SandboxBridge {
790
814
  },
791
815
  },
792
816
  }
793
- this.notify()
794
-
795
817
  const outbound: QueryDataSourceMessage = {
796
818
  type: "queryDataSource",
797
819
  subscriptionId,
@@ -801,6 +823,7 @@ export class SandboxBridge {
801
823
  ...(query.sorts !== undefined ? { sorts: query.sorts } : {}),
802
824
  }
803
825
  this.postToHost(outbound)
826
+ this.notify()
804
827
  }
805
828
 
806
829
  private setDataSourceQueryError(
@@ -840,6 +863,11 @@ export class SandboxBridge {
840
863
  ...this.hostState,
841
864
  dataSourceState,
842
865
  }
866
+ const outbound: UnsubscribeDataSourceQueryMessage = {
867
+ type: "unsubscribeDataSourceQuery",
868
+ subscriptionId,
869
+ }
870
+ this.postToHost(outbound)
843
871
  }
844
872
 
845
873
  postResize(height: number) {
@@ -923,20 +951,22 @@ export class SandboxBridge {
923
951
  }
924
952
 
925
953
  updatePage(args: UpdatePageArgs): Promise<UpdatePageResult> {
954
+ // Prefer is_archived over the deprecated archived argument.
955
+ // TODO(custom-blocks): Remove the archived fallback when upgrading to bridge protocol v4.
956
+ const isArchived = args.is_archived ?? args.archived
926
957
  return new Promise(resolve => {
927
958
  if (
928
959
  (args.properties === undefined ||
929
960
  Object.keys(args.properties).length === 0) &&
930
961
  args.icon === undefined &&
931
962
  args.cover === undefined &&
932
- args.archived === undefined
963
+ isArchived === undefined
933
964
  ) {
934
965
  resolve({
935
966
  status: "error",
936
967
  error: {
937
968
  code: "invalid_page_update",
938
- message:
939
- "updatePage requires at least one of: properties, icon, cover, archived.",
969
+ message: "updatePage requires at least one property.",
940
970
  isRetryable: false,
941
971
  },
942
972
  })
@@ -958,8 +988,8 @@ export class SandboxBridge {
958
988
  if (args.cover !== undefined) {
959
989
  outbound.cover = args.cover
960
990
  }
961
- if (args.archived !== undefined) {
962
- outbound.archived = args.archived
991
+ if (isArchived !== undefined) {
992
+ outbound.archived = isArchived
963
993
  }
964
994
  this.postToHost(outbound)
965
995
  })
@@ -993,7 +1023,7 @@ export class SandboxBridge {
993
1023
  properties: resolvedProperties?.properties,
994
1024
  icon: pageUpdateArgs.icon,
995
1025
  cover: pageUpdateArgs.cover,
996
- archived: pageUpdateArgs.archived,
1026
+ is_archived: pageUpdateArgs.is_archived ?? pageUpdateArgs.archived,
997
1027
  })
998
1028
  }
999
1029
 
@@ -0,0 +1,97 @@
1
+ import type { SubscribeToDataSourceArgs } from "../../types.js"
2
+ import type {
3
+ CustomBlockHostState,
4
+ InitializedHostState,
5
+ } from "../hostState.js"
6
+ import { notifyListener } from "../notifyListener.js"
7
+ import { customBlockDataSources, customBlockHost } from "../sandboxClient.js"
8
+
9
+ export function subscribeToDataSource({
10
+ key,
11
+ onSnapshot,
12
+ options: providedOptions = {},
13
+ }: SubscribeToDataSourceArgs): () => void {
14
+ const options = structuredClone(providedOptions)
15
+ const subscriptionId = customBlockDataSources.createSubscriptionId()
16
+ let active = true
17
+ let lastSnapshotInputs:
18
+ | {
19
+ status: CustomBlockHostState["status"]
20
+ dataSource: InitializedHostState["dataSources"][number] | undefined
21
+ queryState: InitializedHostState["dataSourceState"][string] | undefined
22
+ }
23
+ | undefined
24
+ let lastMatchingSignature: string | null | undefined
25
+
26
+ const deliverSnapshotIfChanged = (
27
+ hostState: CustomBlockHostState,
28
+ dataSource: InitializedHostState["dataSources"][number] | undefined,
29
+ queryState: InitializedHostState["dataSourceState"][string] | undefined,
30
+ ) => {
31
+ if (
32
+ lastSnapshotInputs !== undefined &&
33
+ lastSnapshotInputs.status === hostState.status &&
34
+ lastSnapshotInputs.dataSource === dataSource &&
35
+ lastSnapshotInputs.queryState === queryState
36
+ ) {
37
+ return
38
+ }
39
+ const snapshot = customBlockDataSources.getView(
40
+ hostState,
41
+ key,
42
+ subscriptionId,
43
+ )
44
+ // Record inputs before calling consumer code, which can trigger another notification.
45
+ lastSnapshotInputs = { status: hostState.status, dataSource, queryState }
46
+ notifyListener(() => onSnapshot(snapshot))
47
+ }
48
+
49
+ const handleHostChange = () => {
50
+ if (!active) {
51
+ return
52
+ }
53
+ const hostState = customBlockHost.getState()
54
+ if (hostState.status !== "initialized") {
55
+ deliverSnapshotIfChanged(hostState, undefined, undefined)
56
+ return
57
+ }
58
+
59
+ const dataSource = hostState.dataSources.find(source => source.key === key)
60
+ const signature =
61
+ dataSource === undefined ? null : JSON.stringify(dataSource)
62
+ if (signature !== lastMatchingSignature) {
63
+ const forceRequery = lastMatchingSignature !== undefined
64
+ lastMatchingSignature = signature
65
+ // Querying updates SDK state and calls this listener again.
66
+ customBlockDataSources.query({
67
+ subscriptionId,
68
+ key,
69
+ options,
70
+ forceRequery,
71
+ })
72
+ return
73
+ }
74
+ deliverSnapshotIfChanged(
75
+ hostState,
76
+ dataSource,
77
+ hostState.dataSourceState[subscriptionId],
78
+ )
79
+ }
80
+
81
+ const unsubscribeHost = customBlockHost.subscribe(handleHostChange)
82
+ try {
83
+ handleHostChange()
84
+ } catch (error) {
85
+ unsubscribeHost()
86
+ customBlockDataSources.release(subscriptionId)
87
+ throw error
88
+ }
89
+ return () => {
90
+ if (!active) {
91
+ return
92
+ }
93
+ active = false
94
+ unsubscribeHost()
95
+ customBlockDataSources.release(subscriptionId)
96
+ }
97
+ }
@@ -139,18 +139,21 @@ export function getDataSourceQueryView(
139
139
  propertiesByKey[key] =
140
140
  propertyId === undefined ? undefined : entry.propertiesById[propertyId]
141
141
  }
142
+ const update = (args: NotionDataSourcePageUpdateArgs) =>
143
+ updateDataSourcePage({
144
+ dataSource,
145
+ pageId: entry.id,
146
+ pageUpdateArgs: args,
147
+ })
142
148
  return {
143
149
  id: entry.id,
144
150
  is_archived: entry.is_archived,
145
151
  in_trash: entry.in_trash,
146
152
  propertiesById: entry.propertiesById,
147
153
  propertiesByKey,
148
- update: args =>
149
- updateDataSourcePage({
150
- dataSource,
151
- pageId: entry.id,
152
- pageUpdateArgs: args,
153
- }),
154
+ update,
155
+ archive: () => update({ is_archived: true }),
156
+ unarchive: () => update({ is_archived: false }),
154
157
  }
155
158
  })
156
159
 
@@ -0,0 +1,14 @@
1
+ /** Report consumer errors without interrupting bridge operations or other listeners. */
2
+ export function notifyListener(listener: () => void): void {
3
+ try {
4
+ listener()
5
+ } catch (error) {
6
+ if (typeof globalThis.reportError === "function") {
7
+ globalThis.reportError(error)
8
+ } else {
9
+ setTimeout(() => {
10
+ throw error
11
+ }, 0)
12
+ }
13
+ }
14
+ }
@@ -10,7 +10,6 @@ import type {
10
10
  NotionUserId,
11
11
  UpdatePageArgs,
12
12
  UpdatePageResult,
13
- UseDataSourceOptions,
14
13
  } from "../types.js"
15
14
  import {
16
15
  type CustomBlockHostState,
@@ -18,9 +17,14 @@ import {
18
17
  getDataSourceQueryView as getDataSourceQueryViewWithBridge,
19
18
  } from "./hostState.js"
20
19
  import type { ManifestLoadResult } from "./loadManifest.js"
21
- import { type MessageLogEntry, SandboxBridge } from "./SandboxBridge.js"
20
+ import {
21
+ type MessageLogEntry,
22
+ type QueryDataSourceArgs,
23
+ SandboxBridge,
24
+ } from "./SandboxBridge.js"
22
25
 
23
26
  let bridge: SandboxBridge | undefined
27
+ let didWarnAboutPagesDelete = false
24
28
 
25
29
  function getBridge(): SandboxBridge {
26
30
  if (!bridge) {
@@ -72,12 +76,8 @@ export const customBlockDataSources = {
72
76
  return getBridge().createDataSourceSubscriptionId()
73
77
  },
74
78
 
75
- query: (
76
- subscriptionId: string,
77
- key: string,
78
- options?: UseDataSourceOptions,
79
- ) => {
80
- getBridge().queryDataSource(subscriptionId, key, options)
79
+ query: (args: QueryDataSourceArgs) => {
80
+ getBridge().queryDataSource(args)
81
81
  },
82
82
 
83
83
  release: (subscriptionId: string) => {
@@ -134,10 +134,31 @@ export const pages = {
134
134
  },
135
135
 
136
136
  /**
137
- * Soft-delete a page by archiving it.
137
+ * Archives a page.
138
+ */
139
+ archive: (pageId: NotionPageId): Promise<UpdatePageResult> => {
140
+ return getBridge().updatePage({ pageId, is_archived: true })
141
+ },
142
+
143
+ /**
144
+ * Unarchives a page.
145
+ */
146
+ unarchive: (pageId: NotionPageId): Promise<UpdatePageResult> => {
147
+ return getBridge().updatePage({ pageId, is_archived: false })
148
+ },
149
+
150
+ /**
151
+ * @deprecated This method archives the page. It does not move the page to Trash.
152
+ * Use `pages.archive(pageId)` instead.
138
153
  */
139
154
  delete: (pageId: NotionPageId): Promise<UpdatePageResult> => {
140
- return getBridge().updatePage({ pageId, archived: true })
155
+ if (!didWarnAboutPagesDelete) {
156
+ didWarnAboutPagesDelete = true
157
+ console.warn(
158
+ "[Notion Custom Blocks] DEPRECATED: pages.delete() archives the page. It does not move the page to Trash. Use pages.archive() instead.",
159
+ )
160
+ }
161
+ return getBridge().updatePage({ pageId, is_archived: true })
141
162
  },
142
163
  }
143
164
 
@@ -7,6 +7,7 @@ import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
7
7
  import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
8
8
  import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
9
9
  import { autoResize } from "./autoResize.js"
10
+ import { subscribeToDataSource } from "./bridge/dataSources/subscribe.js"
10
11
  import type {
11
12
  CustomBlockHostState,
12
13
  InitializedHostState,
@@ -72,6 +73,12 @@ export const customBlock = {
72
73
  },
73
74
 
74
75
  autoResize,
76
+
77
+ /**
78
+ * Registers a listener that receives the current query snapshot and later updates.
79
+ * Returns a function that removes this listener. Other listeners remain subscribed.
80
+ */
81
+ subscribeToDataSource,
75
82
  }
76
83
 
77
84
  let lastHostState: CustomBlockHostState | undefined
@@ -1,8 +1,22 @@
1
1
  import { useEffect, useState } from "react"
2
2
  import { getDataSourceQueryOptionsIdentity } from "../bridge/dataSources/query.js"
3
- import { customBlockDataSources } from "../bridge/sandboxClient.js"
3
+ import { customBlock } from "../customBlock.js"
4
4
  import type { UseDataSourceOptions, UseDataSourceResult } from "../types.js"
5
- import { useCustomBlockHost } from "./useHostState.js"
5
+
6
+ const EMPTY_DATA_SOURCE_RESULT: UseDataSourceResult = {
7
+ items: [],
8
+ propertySchemasById: {},
9
+ propertyIdsByKey: {},
10
+ propertySchemasByKey: {},
11
+ isLoading: false,
12
+ hasMore: false,
13
+ }
14
+
15
+ type DataSourceHookState = {
16
+ key: string
17
+ optionsIdentity: string
18
+ snapshot: UseDataSourceResult
19
+ }
6
20
 
7
21
  /**
8
22
  * Reads from the data source mapped to the given semantic `key`.
@@ -18,45 +32,24 @@ export function useDataSource(
18
32
  key: string,
19
33
  options?: UseDataSourceOptions,
20
34
  ): UseDataSourceResult {
21
- const host = useCustomBlockHost()
22
- const [subscriptionId] = useState(() =>
23
- customBlockDataSources.createSubscriptionId(),
24
- )
25
35
  const optionsIdentity = getDataSourceQueryOptionsIdentity(options)
26
- const matchingDataSource =
27
- host.status === "initialized"
28
- ? host.dataSources.find(dataSource => dataSource.key === key)
29
- : undefined
30
-
31
- const isInitialized = host.status === "initialized"
36
+ const [state, setState] = useState<DataSourceHookState>(() => ({
37
+ key,
38
+ optionsIdentity,
39
+ snapshot: EMPTY_DATA_SOURCE_RESULT,
40
+ }))
32
41
  // biome-ignore lint/correctness/useExhaustiveDependencies(options): Compare options by value through optionsIdentity. Object identity alone must not trigger queries.
33
- // biome-ignore lint/correctness/useExhaustiveDependencies(optionsIdentity): Changes to the serialized options must trigger a query even though the callback reads options.
34
- // biome-ignore lint/correctness/useExhaustiveDependencies(matchingDataSource): Requery when the matching data source changes, even when the key and options are unchanged.
35
42
  useEffect(() => {
36
- if (!isInitialized) {
37
- return
38
- }
39
-
40
- customBlockDataSources.query(subscriptionId, key, options)
41
- }, [matchingDataSource, isInitialized, key, optionsIdentity, subscriptionId])
42
-
43
- useEffect(
44
- () => () => {
45
- customBlockDataSources.release(subscriptionId)
46
- },
47
- [subscriptionId],
48
- )
49
-
50
- const view = customBlockDataSources.getView(host, key, subscriptionId)
51
-
52
- return {
53
- items: view.items,
54
- collectionSchema: view.collectionSchema,
55
- propertySchemasById: view.propertySchemasById,
56
- propertyIdsByKey: view.propertyIdsByKey,
57
- propertySchemasByKey: view.propertySchemasByKey,
58
- isLoading: view.isLoading,
59
- hasMore: view.hasMore,
60
- error: view.error,
43
+ return customBlock.subscribeToDataSource({
44
+ key,
45
+ onSnapshot: snapshot => {
46
+ setState({ key, optionsIdentity, snapshot })
47
+ },
48
+ options,
49
+ })
50
+ }, [key, optionsIdentity])
51
+ if (state.key !== key || state.optionsIdentity !== optionsIdentity) {
52
+ return EMPTY_DATA_SOURCE_RESULT
61
53
  }
54
+ return state.snapshot
62
55
  }
package/src/types.ts CHANGED
@@ -80,7 +80,7 @@ export type NotionPagePropertyInputMap = {
80
80
  }
81
81
 
82
82
  /**
83
- * Consumer-facing page shape returned from `useDataSource`. Derived from the bridge payload
83
+ * Consumer-facing page shape returned from a data source snapshot. Derived from the bridge payload
84
84
  * plus the data source's `propertyIdsByKey`.
85
85
  */
86
86
  export type NotionDataSourcePage = {
@@ -106,12 +106,20 @@ export type NotionDataSourcePage = {
106
106
  * keys to raw property IDs before sending the bridge message to the host.
107
107
  */
108
108
  update: (args: NotionDataSourcePageUpdateArgs) => Promise<UpdatePageResult>
109
+ /** Archives this page. Shorthand for update({ is_archived: true }). */
110
+ archive: () => Promise<UpdatePageResult>
111
+ /** Unarchives this page. Shorthand for update({ is_archived: false }). */
112
+ unarchive: () => Promise<UpdatePageResult>
109
113
  }
110
114
 
111
115
  export type NotionDataSourcePageUpdateArgs = {
112
116
  properties?: NotionPagePropertyInputMap
113
117
  icon?: NotionPageIcon
114
118
  cover?: NotionPageCover
119
+ /** Whether to archive this page. Takes precedence over archived. */
120
+ is_archived?: boolean
121
+ /** @deprecated Use is_archived instead. */
122
+ // TODO(custom-blocks): Remove archived when upgrading to bridge protocol v4.
115
123
  archived?: boolean
116
124
  }
117
125
 
@@ -168,14 +176,14 @@ export type NotionDataSourceFilter =
168
176
  | { and: NotionDataSourcePropertyFilter[] }
169
177
 
170
178
  /**
171
- * Return shape of `useDataSource`.
179
+ * Latest live snapshot of a data source query.
172
180
  *
173
181
  * - `items` — the rows the host has returned so far. Empty until the first response arrives.
174
182
  * - `isLoading` — `true` while a query is in flight.
175
- * - `hasMore` — `true` if the host indicated more rows are available beyond the current page.
183
+ * - `hasMore` — `true` if the host indicated more rows are available beyond the requested prefix.
176
184
  * - `error` — structured error information if the most recent query failed.
177
185
  */
178
- export type UseDataSourceResult = {
186
+ export type DataSourceSnapshot = {
179
187
  items: NotionDataSourcePage[]
180
188
  /**
181
189
  * Collection/data source schema for the bound Notion data source, including raw property
@@ -203,22 +211,25 @@ export type UseDataSourceResult = {
203
211
  error?: CustomBlockQueryDataSourceErrorInfo
204
212
  }
205
213
 
206
- export type UseDataSourceOptions = {
207
- /**
208
- * Maximum number of rows to request from the host. Defaults to 20.
209
- */
214
+ export type DataSourceQueryOptions = {
215
+ /** Maximum number of rows to request from the host. Defaults to 20. */
210
216
  limit?: number
211
- /**
212
- * Optional property filter. The SDK resolves semantic property keys before
213
- * it sends the query to the host.
214
- */
217
+ /** Optional property filter. The SDK resolves semantic keys before it sends the query. */
215
218
  filter?: NotionDataSourceFilter
216
- /**
217
- * Optional property sorts. The host applies them in array order.
218
- */
219
+ /** Optional property sorts. The host applies them in array order. */
219
220
  sorts?: NotionDataSourceSort[]
220
221
  }
221
222
 
223
+ export type SubscribeToDataSourceArgs = {
224
+ key: string
225
+ onSnapshot: (snapshot: DataSourceSnapshot) => void
226
+ options?: DataSourceQueryOptions
227
+ }
228
+
229
+ export type UseDataSourceResult = DataSourceSnapshot
230
+
231
+ export type UseDataSourceOptions = DataSourceQueryOptions
232
+
222
233
  /**
223
234
  * Parent reference accepted by `sdk.pages.create`. Mirrors Notion's public `POST /v1/pages`
224
235
  * parent shape; see https://developers.notion.com/reference/data-source.
@@ -260,7 +271,16 @@ export type CreatePageResult = BridgeMessagePayload<
260
271
  CustomBlockCreatePageErrorInfo
261
272
  >
262
273
 
263
- export type UpdatePageArgs = Omit<UpdatePageMessage, "type" | "requestId">
274
+ export type UpdatePageArgs = Omit<
275
+ UpdatePageMessage,
276
+ "type" | "requestId" | "archived"
277
+ > & {
278
+ /** Whether to archive this page. Takes precedence over archived. */
279
+ is_archived?: boolean
280
+ /** @deprecated Use is_archived instead. */
281
+ // TODO(custom-blocks): Remove archived when upgrading to bridge protocol v4.
282
+ archived?: boolean
283
+ }
264
284
 
265
285
  /**
266
286
  * @deprecated Use `UpdatePageArgs` instead.
@@ -278,7 +298,7 @@ export type GetPageResult = BridgeMessagePayload<
278
298
  >
279
299
 
280
300
  /**
281
- * Result of `sdk.pages.update` / `sdk.pages.delete`.
301
+ * Result of `sdk.pages.update`, `sdk.pages.archive`, and `sdk.pages.unarchive`.
282
302
  */
283
303
  export type UpdatePageResult = BridgeMessagePayload<
284
304
  UpdatePageResultMessage,