@notionhq/custom-blocks 0.0.75 → 0.0.77

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 (34) hide show
  1. package/HOST.md +9 -5
  2. package/dist/bridge/SandboxBridge.d.ts +0 -1
  3. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  4. package/dist/bridge/SandboxBridge.js +15 -34
  5. package/dist/bridge/hostState.d.ts +1 -2
  6. package/dist/bridge/hostState.d.ts.map +1 -1
  7. package/dist/bridge/messages/hostToSandbox.d.ts +3 -79
  8. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  9. package/dist/bridge/messages/hostToSandbox.js +2 -3
  10. package/dist/bridge/messages/queryDataSource.d.ts +3 -7
  11. package/dist/bridge/messages/queryDataSource.d.ts.map +1 -1
  12. package/dist/bridge/messages/queryDataSource.js +3 -7
  13. package/dist/bridge/messages/queryDataSourceResult.d.ts +4 -240
  14. package/dist/bridge/messages/queryDataSourceResult.d.ts.map +1 -1
  15. package/dist/bridge/messages/queryDataSourceResult.js +4 -22
  16. package/dist/bridge/messages/sandboxToHost.d.ts +1 -2
  17. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -1
  18. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  19. package/dist/host/createCustomBlockHost.js +25 -20
  20. package/dist/host/queries/querySubscriptions.d.ts +10 -10
  21. package/dist/host/queries/querySubscriptions.d.ts.map +1 -1
  22. package/dist/host/queries/querySubscriptions.js +35 -19
  23. package/dist/host/queries/types.d.ts +5 -4
  24. package/dist/host/queries/types.d.ts.map +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +1 -1
  27. package/src/bridge/SandboxBridge.ts +15 -35
  28. package/src/bridge/hostState.ts +1 -2
  29. package/src/bridge/messages/hostToSandbox.ts +2 -6
  30. package/src/bridge/messages/queryDataSource.ts +3 -7
  31. package/src/bridge/messages/queryDataSourceResult.ts +4 -24
  32. package/src/host/createCustomBlockHost.ts +27 -22
  33. package/src/host/queries/querySubscriptions.ts +58 -39
  34. package/src/host/queries/types.ts +10 -11
@@ -55,7 +55,6 @@ import type { NotionUser } from "./users/user.js"
55
55
  * single host needs to support multiple custom blocks built with different versions of the bridge
56
56
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
57
57
  */
58
- // TODO(custom-blocks): Update when bumping bridge protocol version to 3.
59
58
  export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 2
60
59
 
61
60
  /**
@@ -76,7 +75,6 @@ export class SandboxBridge {
76
75
  private listeners = new Set<() => void>()
77
76
  private messageLog: MessageLogEntry[] = []
78
77
  private messageLogListeners = new Set<() => void>()
79
- private nextRequestId = 1
80
78
  private readonly pendingCreatePage = new PendingRequests<CreatePageResult>(
81
79
  "custom-block-create-page",
82
80
  )
@@ -400,32 +398,23 @@ export class SandboxBridge {
400
398
 
401
399
  case "queryDataSourceResult": {
402
400
  const queryEntry = Object.entries(hostState.dataSourceState).find(
403
- ([, state]) => state.latestSnapshotId === message.snapshotId,
401
+ ([, state]) => state.subscriptionId === message.subscriptionId,
404
402
  )
405
403
  if (queryEntry === undefined) {
406
404
  return
407
405
  }
408
406
  const [key, currentState] = queryEntry
409
- if (currentState.latestRequestId !== message.requestId) {
410
- return
411
- }
412
407
  const queryResult =
413
- "status" in message
414
- ? message.status === "error"
415
- ? {
416
- items: [],
417
- hasMore: false,
418
- error: message.error,
419
- }
420
- : {
421
- items: message.items,
422
- hasMore: message.hasMore,
423
- error: undefined,
424
- }
408
+ message.status === "error"
409
+ ? {
410
+ items: [],
411
+ hasMore: false,
412
+ error: message.error,
413
+ }
425
414
  : {
426
415
  items: message.items,
427
416
  hasMore: message.hasMore,
428
- error: message.error,
417
+ error: undefined,
429
418
  }
430
419
  this.hostState = {
431
420
  ...hostState,
@@ -436,10 +425,7 @@ export class SandboxBridge {
436
425
  isLoading: false,
437
426
  hasMore: queryResult.hasMore,
438
427
  error: queryResult.error,
439
- // Keep the request ID so later host-pushed refreshes for the
440
- // same subscription still match.
441
- latestRequestId: message.requestId,
442
- latestSnapshotId: message.snapshotId,
428
+ subscriptionId: message.subscriptionId,
443
429
  latestLimit: currentState.latestLimit,
444
430
  },
445
431
  },
@@ -558,21 +544,18 @@ export class SandboxBridge {
558
544
  }
559
545
 
560
546
  const limit = resolveDataSourceQueryLimit(options.limit)
561
- const snapshotId = makeDataSourceSnapshotId({
547
+ const subscriptionId = makeDataSourceSubscriptionId({
562
548
  key,
563
549
  })
564
550
 
565
551
  if (
566
552
  currentState.isLoading &&
567
- currentState.latestSnapshotId === snapshotId &&
553
+ currentState.subscriptionId === subscriptionId &&
568
554
  currentState.latestLimit === limit
569
555
  ) {
570
556
  return
571
557
  }
572
558
 
573
- const requestId = `custom-block-query-${this.nextRequestId}`
574
- this.nextRequestId += 1
575
-
576
559
  this.hostState = {
577
560
  ...this.hostState,
578
561
  dataSourceState: {
@@ -581,8 +564,7 @@ export class SandboxBridge {
581
564
  ...currentState,
582
565
  isLoading: true,
583
566
  error: undefined,
584
- latestRequestId: requestId,
585
- latestSnapshotId: snapshotId,
567
+ subscriptionId,
586
568
  latestLimit: limit,
587
569
  },
588
570
  },
@@ -591,8 +573,7 @@ export class SandboxBridge {
591
573
 
592
574
  const outbound: QueryDataSourceMessage = {
593
575
  type: "queryDataSource",
594
- requestId,
595
- snapshotId,
576
+ subscriptionId,
596
577
  dataSourceId: dataSource.collectionPointer.id,
597
578
  limit,
598
579
  }
@@ -615,8 +596,7 @@ export class SandboxBridge {
615
596
  ...currentState,
616
597
  isLoading: false,
617
598
  error,
618
- latestRequestId: undefined,
619
- latestSnapshotId: undefined,
599
+ subscriptionId: undefined,
620
600
  latestLimit: undefined,
621
601
  },
622
602
  },
@@ -907,7 +887,7 @@ function resolveDataSourceQueryLimit(limit: number | undefined): number {
907
887
  return limit
908
888
  }
909
889
 
910
- function makeDataSourceSnapshotId(args: { key: string }): string {
890
+ function makeDataSourceSubscriptionId(args: { key: string }): string {
911
891
  const { key } = args
912
892
  return `data-source:${encodeURIComponent(key)}`
913
893
  }
@@ -38,8 +38,7 @@ export type DataSourceQueryState = {
38
38
  isLoading: boolean
39
39
  hasMore: boolean
40
40
  error?: CustomBlockQueryDataSourceErrorInfo
41
- latestRequestId?: string
42
- latestSnapshotId?: string
41
+ subscriptionId?: string
43
42
  latestLimit?: number
44
43
  }
45
44
 
@@ -9,10 +9,7 @@ import { invalidSandboxMessageSchema } from "./invalidSandboxMessage.js"
9
9
  import { listUsersResultMessageSchema } from "./listUsers.js"
10
10
  import { pageChangedMessageSchema } from "./pageChanged.js"
11
11
  import { parentChangedMessageSchema } from "./parentChanged.js"
12
- import {
13
- legacyQueryDataSourceResultMessageSchema,
14
- statusQueryDataSourceResultMessageSchema,
15
- } from "./queryDataSourceResult.js"
12
+ import { queryDataSourceResultMessageSchema } from "./queryDataSourceResult.js"
16
13
  import { themeChangedMessageSchema } from "./themeChanged.js"
17
14
  import { updatePageResultMessageSchema } from "./updatePageResult.js"
18
15
 
@@ -26,8 +23,7 @@ export const hostToSandboxMessageSchema = v.variant("type", [
26
23
  pageChangedMessageSchema,
27
24
  currentUserChangedMessageSchema,
28
25
  dataSourcesChangedMessageSchema,
29
- statusQueryDataSourceResultMessageSchema,
30
- legacyQueryDataSourceResultMessageSchema,
26
+ queryDataSourceResultMessageSchema,
31
27
  createPageResultMessageSchema,
32
28
  getPageResultMessageSchema,
33
29
  getUserResultMessageSchema,
@@ -7,15 +7,11 @@ import { notionDataSourceIdSchema } from "../ids.js"
7
7
  */
8
8
  export const queryDataSourceMessageSchema = v.object({
9
9
  type: v.literal("queryDataSource"),
10
- requestId: v.string(),
11
- /**
12
- * The stable snapshot ID that identifies the SDK result slot to update. This is stable for the
13
- * lifetime of the subscription.
14
- */
15
- snapshotId: v.string(),
10
+ /** Stable for the lifetime of this query subscription. */
11
+ subscriptionId: v.string(),
16
12
  /** The raw Notion data source ID to read from. */
17
13
  dataSourceId: notionDataSourceIdSchema,
18
- /** The number of items to return in the snapshot. */
14
+ /** The number of items to return in each subscription update. */
19
15
  limit: v.number(),
20
16
  })
21
17
 
@@ -22,44 +22,24 @@ export const customBlockQueryDataSourceErrorInfoSchema = v.object({
22
22
  })
23
23
 
24
24
  /**
25
- * Message sent by the host to the sandbox in response to a `queryDataSource` request.
25
+ * Message sent by the host to update a `queryDataSource` subscription.
26
26
  */
27
- export const legacyQueryDataSourceResultMessageSchema = v.object({
28
- type: v.literal("queryDataSourceResult"),
29
- requestId: v.string(),
30
- snapshotId: v.string(),
31
- items: v.array(notionDataSourcePageBridgeSchema),
32
- hasMore: v.boolean(),
33
- error: v.optional(customBlockQueryDataSourceErrorInfoSchema),
34
- })
35
-
36
- export const statusQueryDataSourceResultMessageSchema = v.variant("status", [
27
+ export const queryDataSourceResultMessageSchema = v.variant("status", [
37
28
  v.object({
38
29
  type: v.literal("queryDataSourceResult"),
39
- requestId: v.string(),
40
- snapshotId: v.string(),
30
+ subscriptionId: v.string(),
41
31
  status: v.literal("success"),
42
32
  items: v.array(notionDataSourcePageBridgeSchema),
43
33
  hasMore: v.boolean(),
44
34
  }),
45
35
  v.object({
46
36
  type: v.literal("queryDataSourceResult"),
47
- requestId: v.string(),
48
- snapshotId: v.string(),
37
+ subscriptionId: v.string(),
49
38
  status: v.literal("error"),
50
39
  error: customBlockQueryDataSourceErrorInfoSchema,
51
40
  }),
52
41
  ])
53
42
 
54
- /**
55
- * TODO(custom-blocks): Update when bumping bridge protocol version to 3.
56
- * Protocol version 2 hosts and sandboxes use the legacy optional-error shape.
57
- */
58
- export const queryDataSourceResultMessageSchema = v.union([
59
- legacyQueryDataSourceResultMessageSchema,
60
- statusQueryDataSourceResultMessageSchema,
61
- ])
62
-
63
43
  export type QueryDataSourceResultMessage = v.InferOutput<
64
44
  typeof queryDataSourceResultMessageSchema
65
45
  >
@@ -111,20 +111,24 @@ export function createCustomBlockHost(
111
111
  }
112
112
 
113
113
  function postQueryDataSourceResult(args: {
114
- requestId: string
115
- snapshotId: string
114
+ subscriptionId: string
116
115
  response: CustomBlockHostQueryDataSourceResult
117
116
  }) {
118
- // TODO(custom-blocks): Update when bumping bridge protocol version to 3.
119
- // Protocol v2 sandboxes require the legacy optional-error result shape.
120
- const message: QueryDataSourceResultMessage = {
121
- type: "queryDataSourceResult",
122
- requestId: args.requestId,
123
- snapshotId: args.snapshotId,
124
- items: args.response.items,
125
- hasMore: args.response.hasMore ?? false,
126
- error: args.response.error,
127
- }
117
+ const message: QueryDataSourceResultMessage =
118
+ args.response.status === "error"
119
+ ? {
120
+ type: "queryDataSourceResult",
121
+ subscriptionId: args.subscriptionId,
122
+ status: "error",
123
+ error: args.response.error,
124
+ }
125
+ : {
126
+ type: "queryDataSourceResult",
127
+ subscriptionId: args.subscriptionId,
128
+ status: "success",
129
+ items: args.response.items,
130
+ hasMore: args.response.hasMore,
131
+ }
128
132
  post(message)
129
133
  }
130
134
 
@@ -183,11 +187,13 @@ export function createCustomBlockHost(
183
187
  }
184
188
 
185
189
  async function handleQuery(message: QueryDataSourceMessage) {
186
- querySubscriptions.track(message)
190
+ const token = querySubscriptions.track(message)
187
191
  const response = await handlers.queryDataSource(message)
192
+ if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
193
+ return
194
+ }
188
195
  postQueryDataSourceResult({
189
- requestId: message.requestId,
190
- snapshotId: message.snapshotId,
196
+ subscriptionId: message.subscriptionId,
191
197
  response,
192
198
  })
193
199
  }
@@ -362,6 +368,7 @@ export function createCustomBlockHost(
362
368
  window.removeEventListener("message", onMessage)
363
369
  iframe.removeEventListener("load", onIframeLoad)
364
370
  clearNoReadyTimeout()
371
+ querySubscriptions.clear()
365
372
  },
366
373
  post,
367
374
  setTheme(nextTheme) {
@@ -392,19 +399,17 @@ export function createCustomBlockHost(
392
399
  post({ type: "currentUserChanged", currentUser })
393
400
  },
394
401
  refreshQuery({ dataSourceId, response }) {
395
- const refresh = querySubscriptions.resolveRefresh({
402
+ const refreshes = querySubscriptions.resolveRefreshes({
396
403
  dataSourceId,
397
404
  response,
398
405
  })
399
- if (refresh.status === "missing") {
406
+ if (refreshes.length === 0) {
400
407
  emit("warn", `No active query exists for data source "${dataSourceId}"`)
401
408
  return
402
409
  }
403
- postQueryDataSourceResult({
404
- requestId: refresh.requestId,
405
- snapshotId: refresh.snapshotId,
406
- response: refresh.response,
407
- })
410
+ for (const refresh of refreshes) {
411
+ postQueryDataSourceResult(refresh)
412
+ }
408
413
  },
409
414
  }
410
415
  }
@@ -1,52 +1,71 @@
1
1
  import type { NotionDataSourceId } from "../../bridge/ids.js"
2
2
  import type { QueryDataSourceMessage } from "../../bridge/messages/queryDataSource.js"
3
- import type {
4
- CustomBlockHostQueryDataSourceResult,
5
- QuerySubscriptionState,
6
- } from "./types.js"
7
-
8
- export type QueryRefreshResult =
9
- | {
10
- status: "success"
11
- requestId: string
12
- snapshotId: string
13
- response: CustomBlockHostQueryDataSourceResult
14
- }
15
- | { status: "missing" }
3
+ import type { CustomBlockHostQueryDataSourceResult } from "./types.js"
4
+
5
+ export type QueryRefresh = {
6
+ subscriptionId: string
7
+ response: CustomBlockHostQueryDataSourceResult
8
+ }
9
+
10
+ type QuerySubscriptionToken = object
11
+
12
+ type QuerySubscriptionState = {
13
+ dataSourceId: NotionDataSourceId
14
+ limit: number
15
+ token: QuerySubscriptionToken
16
+ }
16
17
 
17
18
  export class QuerySubscriptions {
18
- private readonly latestByDataSourceId = new Map<
19
- NotionDataSourceId,
20
- QuerySubscriptionState
21
- >()
22
-
23
- track(message: QueryDataSourceMessage) {
24
- this.latestByDataSourceId.set(message.dataSourceId, {
25
- requestId: message.requestId,
26
- snapshotId: message.snapshotId,
19
+ private readonly bySubscriptionId = new Map<string, QuerySubscriptionState>()
20
+
21
+ track(message: QueryDataSourceMessage): QuerySubscriptionToken {
22
+ const token = {}
23
+ this.bySubscriptionId.set(message.subscriptionId, {
24
+ dataSourceId: message.dataSourceId,
27
25
  limit: message.limit,
26
+ token,
28
27
  })
28
+ return token
29
+ }
30
+
31
+ isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean {
32
+ return this.bySubscriptionId.get(subscriptionId)?.token === token
29
33
  }
30
34
 
31
- resolveRefresh(args: {
35
+ resolveRefreshes(args: {
32
36
  dataSourceId: NotionDataSourceId
33
37
  response: CustomBlockHostQueryDataSourceResult
34
- }): QueryRefreshResult {
35
- const queryState = this.latestByDataSourceId.get(args.dataSourceId)
36
- if (!queryState) {
37
- return { status: "missing" }
38
- }
39
- const items = args.response.items.slice(0, queryState.limit)
40
- return {
41
- status: "success",
42
- requestId: queryState.requestId,
43
- snapshotId: queryState.snapshotId,
44
- response: {
45
- items,
46
- hasMore:
47
- args.response.hasMore ?? args.response.items.length > items.length,
48
- error: args.response.error,
49
- },
38
+ }): QueryRefresh[] {
39
+ const refreshes: QueryRefresh[] = []
40
+ for (const [subscriptionId, queryState] of this.bySubscriptionId) {
41
+ if (queryState.dataSourceId !== args.dataSourceId) {
42
+ continue
43
+ }
44
+ queryState.token = {}
45
+ refreshes.push({
46
+ subscriptionId,
47
+ response: limitResponse(args.response, queryState.limit),
48
+ })
50
49
  }
50
+ return refreshes
51
+ }
52
+
53
+ clear() {
54
+ this.bySubscriptionId.clear()
55
+ }
56
+ }
57
+
58
+ function limitResponse(
59
+ response: CustomBlockHostQueryDataSourceResult,
60
+ limit: number,
61
+ ): CustomBlockHostQueryDataSourceResult {
62
+ if (response.status === "error") {
63
+ return response
64
+ }
65
+ const items = response.items.slice(0, limit)
66
+ return {
67
+ status: "success",
68
+ items,
69
+ hasMore: response.hasMore || response.items.length > items.length,
51
70
  }
52
71
  }
@@ -1,14 +1,13 @@
1
1
  import type { NotionDataSourcePageBridge } from "../../bridge/dataSources/dataSourcePage.js"
2
- import type { QueryDataSourceMessage } from "../../bridge/messages/queryDataSource.js"
3
2
  import type { CustomBlockQueryDataSourceErrorInfo } from "../../bridge/messages/queryDataSourceResult.js"
4
3
 
5
- export type CustomBlockHostQueryDataSourceResult = {
6
- items: NotionDataSourcePageBridge[]
7
- hasMore?: boolean
8
- error?: CustomBlockQueryDataSourceErrorInfo
9
- }
10
-
11
- export type QuerySubscriptionState = Pick<
12
- QueryDataSourceMessage,
13
- "requestId" | "snapshotId" | "limit"
14
- >
4
+ export type CustomBlockHostQueryDataSourceResult =
5
+ | {
6
+ status: "success"
7
+ items: NotionDataSourcePageBridge[]
8
+ hasMore: boolean
9
+ }
10
+ | {
11
+ status: "error"
12
+ error: CustomBlockQueryDataSourceErrorInfo
13
+ }