@notionhq/custom-blocks-host 0.2.4 → 0.2.5

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/README.md CHANGED
@@ -246,7 +246,10 @@ The returned handle exposes:
246
246
 
247
247
  State updates are sent after `initResult.success`. Updates made earlier are held until
248
248
  initialization completes. `refreshQuery()` re-runs `queryDataSource` for every active subscription
249
- with the supplied data source ID, preserving each subscription's filter, sorts, and limit.
249
+ with the supplied data source ID, preserving each subscription's filter, sorts, and limit. If that
250
+ query is already running, refresh requests coalesce into one follow-up query. The running query
251
+ delivers its snapshot before the follow-up starts, so continuous changes do not starve updates.
252
+ Unsubscribing, replacing the query, or stopping the host discards its queued refresh.
250
253
 
251
254
  After `stop()`, the host drops outbound messages, including pending request responses. The runtime
252
255
  removes a subscription when it receives `unsubscribeDataSourceQuery`. Repeated unsubscribe messages
@@ -1 +1 @@
1
- {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAA;AAI5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAiBvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAA;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAKzC,OAAO,KAAK,EACX,iCAAiC,EACjC,8BAA8B,EAI9B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EACX,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAe5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IACnD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1E,eAAe,EAAE,8BAA8B,CAAA;IAC/C,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC/D,eAAe,EAAE,CAChB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,KACrD,IAAI,CAAA;IACT,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAK,IAAI,CAAA;IACjE,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAC1E,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;KACzD,KAAK,IAAI,CAAA;CACV,CAAA;AAQD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CAgYvB"}
1
+ {"version":3,"file":"createCustomBlockHost.d.ts","sourceRoot":"","sources":["../src/createCustomBlockHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAA;AAI5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAA;AAiBvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAA;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAKzC,OAAO,KAAK,EACX,iCAAiC,EACjC,8BAA8B,EAI9B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EACX,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,EAC3B,MAAM,qBAAqB,CAAA;AAe5B,MAAM,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,iBAAiB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,oHAAoH;IACpH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAA;IACpD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,mBAAmB,CAAA;QAC7B,aAAa,CAAC,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAA;IACnD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,2BAA2B,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1E,eAAe,EAAE,8BAA8B,CAAA;IAC/C,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC/D,eAAe,EAAE,CAChB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,KACrD,IAAI,CAAA;IACT,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,KAAK,IAAI,CAAA;IAClE,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAK,IAAI,CAAA;IACjE,cAAc,EAAE,CAAC,WAAW,EAAE,iCAAiC,KAAK,IAAI,CAAA;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAC1E,YAAY,EAAE,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;KACzD,KAAK,IAAI,CAAA;CACV,CAAA;AAQD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,sBAAsB,GAC7B,qBAAqB,CAuYvB"}
@@ -109,25 +109,33 @@ export function createCustomBlockHost(options) {
109
109
  sendInitializedMessage(message);
110
110
  }
111
111
  async function handleQuery(message, token = querySubscriptions.subscribe(message)) {
112
- const collectionSchema = getCollectionSchema(activeDataSources, message.dataSourceId);
113
- const queryError = getQueryValidationError(message, collectionSchema);
114
- if (queryError !== undefined) {
115
- if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
116
- postQueryDataSourceResult({
117
- subscriptionId: message.subscriptionId,
118
- response: { status: "error", error: queryError },
119
- });
112
+ try {
113
+ const collectionSchema = getCollectionSchema(activeDataSources, message.dataSourceId);
114
+ const queryError = getQueryValidationError(message, collectionSchema);
115
+ if (queryError !== undefined) {
116
+ if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
117
+ postQueryDataSourceResult({
118
+ subscriptionId: message.subscriptionId,
119
+ response: { status: "error", error: queryError },
120
+ });
121
+ }
122
+ return;
120
123
  }
121
- return;
124
+ const response = await handlers.queryDataSource(message);
125
+ if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
126
+ return;
127
+ }
128
+ postQueryDataSourceResult({
129
+ subscriptionId: message.subscriptionId,
130
+ response,
131
+ });
122
132
  }
123
- const response = await handlers.queryDataSource(message);
124
- if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
125
- return;
133
+ finally {
134
+ const refresh = querySubscriptions.finish(message.subscriptionId, token);
135
+ if (refresh !== undefined) {
136
+ void handleQuery(refresh.message, refresh.token);
137
+ }
126
138
  }
127
- postQueryDataSourceResult({
128
- subscriptionId: message.subscriptionId,
129
- response,
130
- });
131
139
  }
132
140
  function updateHostState(update) {
133
141
  if (initializationController.status === "stopped" ||
@@ -313,7 +321,7 @@ export function createCustomBlockHost(options) {
313
321
  }
314
322
  const normalizedDataSourceId = v.parse(notionDataSourceIdSchema, dataSourceId);
315
323
  const refreshes = querySubscriptions.refresh(normalizedDataSourceId);
316
- if (refreshes.length === 0) {
324
+ if (refreshes === undefined) {
317
325
  emit("warn", `No active query exists for data source "${dataSourceId}"`);
318
326
  return;
319
327
  }
@@ -12,7 +12,8 @@ export declare class QuerySubscriptions {
12
12
  subscribe(message: QueryDataSourceMessage): QuerySubscriptionToken;
13
13
  isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean;
14
14
  unsubscribe(subscriptionId: string): void;
15
- refresh(dataSourceId: NotionDataSourceId): QueryRefresh[];
15
+ refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] | undefined;
16
+ finish(subscriptionId: string, token: QuerySubscriptionToken): QueryRefresh | undefined;
16
17
  unsubscribeAll(): void;
17
18
  }
18
19
  //# sourceMappingURL=querySubscriptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AAOD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CASjE;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAgBxD;IAED,cAAc,SAEb;CACD"}
1
+ {"version":3,"file":"querySubscriptions.d.ts","sourceRoot":"","sources":["../../src/queries/querySubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAA;AAE1G,MAAM,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,KAAK,EAAE,sBAAsB,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACvC,CAAA;AASD,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAE7E,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB,CAWjE;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAExE;IAED,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE,GAAG,SAAS,CAmBpE;IAED,MAAM,CACL,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,sBAAsB,GAC3B,YAAY,GAAG,SAAS,CAY1B;IAED,cAAc,SAEb;CACD"}
@@ -7,6 +7,8 @@ export class QuerySubscriptions {
7
7
  this.bySubscriptionId.set(message.subscriptionId, {
8
8
  message,
9
9
  token,
10
+ isRefreshing: true,
11
+ shouldRefreshAgain: false,
10
12
  });
11
13
  return token;
12
14
  }
@@ -18,20 +20,36 @@ export class QuerySubscriptions {
18
20
  }
19
21
  refresh(dataSourceId) {
20
22
  const refreshes = [];
23
+ let hasSubscriptions = false;
21
24
  for (const queryState of this.bySubscriptionId.values()) {
22
25
  if (queryState.message.dataSourceId !== dataSourceId) {
23
26
  continue;
24
27
  }
25
- const token = {
26
- _tag: "QuerySubscriptionToken",
27
- };
28
- queryState.token = token;
28
+ hasSubscriptions = true;
29
+ if (queryState.isRefreshing) {
30
+ queryState.shouldRefreshAgain = true;
31
+ continue;
32
+ }
33
+ queryState.isRefreshing = true;
29
34
  refreshes.push({
30
35
  message: queryState.message,
31
- token,
36
+ token: queryState.token,
32
37
  });
33
38
  }
34
- return refreshes;
39
+ return hasSubscriptions ? refreshes : undefined;
40
+ }
41
+ finish(subscriptionId, token) {
42
+ const state = this.bySubscriptionId.get(subscriptionId);
43
+ if (state?.token !== token) {
44
+ return undefined;
45
+ }
46
+ state.isRefreshing = false;
47
+ if (!state.shouldRefreshAgain) {
48
+ return undefined;
49
+ }
50
+ state.shouldRefreshAgain = false;
51
+ state.isRefreshing = true;
52
+ return { message: state.message, token: state.token };
35
53
  }
36
54
  unsubscribeAll() {
37
55
  this.bySubscriptionId.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-host",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Reusable host runtime for Notion custom blocks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -247,28 +247,35 @@ export function createCustomBlockHost(
247
247
  message: QueryDataSourceMessage,
248
248
  token: QuerySubscriptionToken = querySubscriptions.subscribe(message),
249
249
  ) {
250
- const collectionSchema = getCollectionSchema(
251
- activeDataSources,
252
- message.dataSourceId,
253
- )
254
- const queryError = getQueryValidationError(message, collectionSchema)
255
- if (queryError !== undefined) {
256
- if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
257
- postQueryDataSourceResult({
258
- subscriptionId: message.subscriptionId,
259
- response: { status: "error", error: queryError },
260
- })
250
+ try {
251
+ const collectionSchema = getCollectionSchema(
252
+ activeDataSources,
253
+ message.dataSourceId,
254
+ )
255
+ const queryError = getQueryValidationError(message, collectionSchema)
256
+ if (queryError !== undefined) {
257
+ if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
258
+ postQueryDataSourceResult({
259
+ subscriptionId: message.subscriptionId,
260
+ response: { status: "error", error: queryError },
261
+ })
262
+ }
263
+ return
264
+ }
265
+ const response = await handlers.queryDataSource(message)
266
+ if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
267
+ return
268
+ }
269
+ postQueryDataSourceResult({
270
+ subscriptionId: message.subscriptionId,
271
+ response,
272
+ })
273
+ } finally {
274
+ const refresh = querySubscriptions.finish(message.subscriptionId, token)
275
+ if (refresh !== undefined) {
276
+ void handleQuery(refresh.message, refresh.token)
261
277
  }
262
- return
263
- }
264
- const response = await handlers.queryDataSource(message)
265
- if (!querySubscriptions.isCurrent(message.subscriptionId, token)) {
266
- return
267
278
  }
268
- postQueryDataSourceResult({
269
- subscriptionId: message.subscriptionId,
270
- response,
271
- })
272
279
  }
273
280
 
274
281
  function updateHostState(update: HostStateUpdates) {
@@ -487,7 +494,7 @@ export function createCustomBlockHost(
487
494
  dataSourceId,
488
495
  )
489
496
  const refreshes = querySubscriptions.refresh(normalizedDataSourceId)
490
- if (refreshes.length === 0) {
497
+ if (refreshes === undefined) {
491
498
  emit("warn", `No active query exists for data source "${dataSourceId}"`)
492
499
  return
493
500
  }
@@ -13,6 +13,8 @@ export type QuerySubscriptionToken = {
13
13
  type QuerySubscriptionState = {
14
14
  message: QueryDataSourceMessage
15
15
  token: QuerySubscriptionToken
16
+ isRefreshing: boolean
17
+ shouldRefreshAgain: boolean
16
18
  }
17
19
 
18
20
  export class QuerySubscriptions {
@@ -25,6 +27,8 @@ export class QuerySubscriptions {
25
27
  this.bySubscriptionId.set(message.subscriptionId, {
26
28
  message,
27
29
  token,
30
+ isRefreshing: true,
31
+ shouldRefreshAgain: false,
28
32
  })
29
33
  return token
30
34
  }
@@ -37,22 +41,42 @@ export class QuerySubscriptions {
37
41
  this.bySubscriptionId.delete(subscriptionId)
38
42
  }
39
43
 
40
- refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] {
44
+ refresh(dataSourceId: NotionDataSourceId): QueryRefresh[] | undefined {
41
45
  const refreshes: QueryRefresh[] = []
46
+ let hasSubscriptions = false
42
47
  for (const queryState of this.bySubscriptionId.values()) {
43
48
  if (queryState.message.dataSourceId !== dataSourceId) {
44
49
  continue
45
50
  }
46
- const token: QuerySubscriptionToken = {
47
- _tag: "QuerySubscriptionToken",
51
+ hasSubscriptions = true
52
+ if (queryState.isRefreshing) {
53
+ queryState.shouldRefreshAgain = true
54
+ continue
48
55
  }
49
- queryState.token = token
56
+ queryState.isRefreshing = true
50
57
  refreshes.push({
51
58
  message: queryState.message,
52
- token,
59
+ token: queryState.token,
53
60
  })
54
61
  }
55
- return refreshes
62
+ return hasSubscriptions ? refreshes : undefined
63
+ }
64
+
65
+ finish(
66
+ subscriptionId: string,
67
+ token: QuerySubscriptionToken,
68
+ ): QueryRefresh | undefined {
69
+ const state = this.bySubscriptionId.get(subscriptionId)
70
+ if (state?.token !== token) {
71
+ return undefined
72
+ }
73
+ state.isRefreshing = false
74
+ if (!state.shouldRefreshAgain) {
75
+ return undefined
76
+ }
77
+ state.shouldRefreshAgain = false
78
+ state.isRefreshing = true
79
+ return { message: state.message, token: state.token }
56
80
  }
57
81
 
58
82
  unsubscribeAll() {