@notionhq/custom-blocks 0.1.32 → 0.1.34
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/dist/bridge/SandboxBridge.d.ts +4 -1
- package/dist/bridge/SandboxBridge.d.ts.map +1 -1
- package/dist/bridge/SandboxBridge.js +95 -57
- package/dist/bridge/dataSources/query.d.ts +26 -0
- package/dist/bridge/dataSources/query.d.ts.map +1 -0
- package/dist/bridge/dataSources/query.js +377 -0
- package/dist/bridge/hostState.d.ts +4 -3
- package/dist/bridge/hostState.d.ts.map +1 -1
- package/dist/bridge/hostState.js +7 -3
- package/dist/bridge/sandboxClient.d.ts +4 -2
- package/dist/bridge/sandboxClient.d.ts.map +1 -1
- package/dist/bridge/sandboxClient.js +10 -4
- package/dist/protocol/messages/init.d.ts +1 -1
- package/dist/protocol/messages/init.d.ts.map +1 -1
- package/dist/protocol/messages/initResult.d.ts +8 -1
- package/dist/protocol/messages/initResult.d.ts.map +1 -1
- package/dist/protocol/messages/queryDataSource.d.ts +955 -0
- package/dist/protocol/messages/queryDataSource.d.ts.map +1 -1
- package/dist/protocol/messages/queryDataSource.js +100 -0
- package/dist/protocol/messages/queryDataSourceResult.d.ts +1 -1
- package/dist/protocol/messages/queryDataSourceResult.d.ts.map +1 -1
- package/dist/protocol/messages/sandboxToHost.d.ts +349 -0
- package/dist/protocol/messages/sandboxToHost.d.ts.map +1 -1
- package/dist/react/useDataSource.d.ts.map +1 -1
- package/dist/react/useDataSource.js +10 -5
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/docs/data-sources.md +39 -3
- package/docs/errors.md +16 -0
- package/docs/lifecycle.md +74 -20
- package/package.json +1 -1
- package/src/bridge/SandboxBridge.ts +112 -64
- package/src/bridge/dataSources/query.ts +470 -0
- package/src/bridge/hostState.ts +11 -3
- package/src/bridge/sandboxClient.ts +20 -4
- package/src/react/useDataSource.ts +16 -5
- package/src/types.ts +47 -0
package/docs/data-sources.md
CHANGED
|
@@ -41,7 +41,10 @@ Use `row.update(...)` whenever you already have a row in hand. For pages you don
|
|
|
41
41
|
```ts
|
|
42
42
|
function useDataSource(
|
|
43
43
|
key: string,
|
|
44
|
-
options?: {
|
|
44
|
+
options?: {
|
|
45
|
+
limit?: number;
|
|
46
|
+
filter?: NotionDataSourceFilter;
|
|
47
|
+
},
|
|
45
48
|
): UseDataSourceResult;
|
|
46
49
|
|
|
47
50
|
type UseDataSourceResult = {
|
|
@@ -56,7 +59,36 @@ type UseDataSourceResult = {
|
|
|
56
59
|
};
|
|
57
60
|
```
|
|
58
61
|
|
|
59
|
-
Reads the data source mapped to `key`. `limit`
|
|
62
|
+
Reads the data source mapped to `key`. The SDK uses a default `limit` of 20 and caps it at 999. To request more rows, store the limit in component state and pass it to `useDataSource` again. Each request is bounded. The hook does not provide cursor pagination.
|
|
63
|
+
|
|
64
|
+
`filter` accepts one property condition or one shallow `and` group. Use `key` for a semantic property key. Use `propertyId` for a raw Notion property ID. The SDK resolves semantic keys before it sends the bridge request. The SDK reports local errors for unknown or unbound keys, property-type mismatches, unsupported filter fields, invalid operators, and invalid values.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const query = useDataSource("tasks", {
|
|
68
|
+
filter: {
|
|
69
|
+
and: [
|
|
70
|
+
{ key: "title", title: { contains: "launch" } },
|
|
71
|
+
{ key: "done", checkbox: { equals: false } },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
limit: 50,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Filters support these property types:
|
|
79
|
+
|
|
80
|
+
- Text properties: `title`, `rich_text`, `url`, `email`, and `phone_number`. They support `equals`, `does_not_equal`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `is_empty`, and `is_not_empty`.
|
|
81
|
+
- `number`: `equals`, `does_not_equal`, `greater_than`, `less_than`, `greater_than_or_equal_to`, `less_than_or_equal_to`, `is_empty`, and `is_not_empty`.
|
|
82
|
+
- `checkbox`: `equals` and `does_not_equal`.
|
|
83
|
+
- `select` and `status`: `equals`, `does_not_equal`, `is_empty`, and `is_not_empty`.
|
|
84
|
+
- `multi_select`: `contains`, `contains_all`, `does_not_contain`, `is_empty`, and `is_not_empty`.
|
|
85
|
+
- `date`: `equals`, `before`, `after`, `on_or_before`, `on_or_after`, `is_empty`, and `is_not_empty`.
|
|
86
|
+
|
|
87
|
+
Select, multi-select, and status values can be one option name or an array of option names. For multi-select properties, `contains` matches any supplied option and `contains_all` requires every supplied option. Empty checks use `{ is_empty: true }` or `{ is_not_empty: true }`. Date comparisons accept ISO dates or ISO timestamps.
|
|
88
|
+
|
|
89
|
+
The `and` group can contain up to 25 conditions. An empty group is valid. Nested groups and `or` groups are not supported.
|
|
90
|
+
|
|
91
|
+
`propertyIdsByKey` maps manifest property keys to raw Notion property IDs; unbound keys map to `undefined`. `propertySchemasByKey` exposes the corresponding schemas and is likewise `undefined` for declared-but-unbound slots.
|
|
60
92
|
|
|
61
93
|
Query failures follow the SDK's [error-handling contract](./errors.md).
|
|
62
94
|
|
|
@@ -161,7 +193,11 @@ export function ScoreList() {
|
|
|
161
193
|
- `NotionDataSourcePageUpdateArgs` / `UpdatePageResult` — arguments and result for the per-page `update` helper.
|
|
162
194
|
- `NotionDataSourcePageUpdateInput` — deprecated alias for `NotionDataSourcePageUpdateArgs`.
|
|
163
195
|
- `NotionDataSourcePageUpdateResult` — deprecated alias for `UpdatePageResult`.
|
|
164
|
-
- `UseDataSourceOptions` — options accepted by `useDataSource
|
|
196
|
+
- `UseDataSourceOptions` — options accepted by `useDataSource`: `limit?: number` and `filter?: NotionDataSourceFilter`.
|
|
197
|
+
- `NotionDataSourceFilter` — one property condition or one shallow `and` group.
|
|
198
|
+
- `NotionDataSourcePropertyFilter` — one property address combined with a type-specific operator.
|
|
199
|
+
- `NotionDataSourcePropertyAddress` — either a semantic property `key` or a raw `propertyId`.
|
|
200
|
+
- `NotionDataSourceTextFilterOperator`, `NotionDataSourceNumberFilterOperator`, `NotionDataSourceCheckboxFilterOperator`, `NotionDataSourceOptionFilterOperator`, `NotionDataSourceContainsFilterOperator`, and `NotionDataSourceDateFilterOperator` — the operators accepted for each property type.
|
|
165
201
|
|
|
166
202
|
### Property schemas
|
|
167
203
|
|
package/docs/errors.md
CHANGED
|
@@ -39,6 +39,22 @@ Unlike request helpers, `initCustomBlock` rejects with `CustomBlockInitializatio
|
|
|
39
39
|
|
|
40
40
|
`CustomBlockInitializationErrorInfo` is the matching `{ code, message, isRetryable }` object type.
|
|
41
41
|
|
|
42
|
+
Initialization errors have direction-specific meanings:
|
|
43
|
+
|
|
44
|
+
- `invalid_connect_payload` means that the host could not parse the SDK's
|
|
45
|
+
`connect` message. The host sends this code in `init.error` when it can read
|
|
46
|
+
the `initializationId`.
|
|
47
|
+
- `invalid_init_payload` means that the SDK could not parse the host's `init`
|
|
48
|
+
message. The SDK sends this code in `initResult.error` when it can read the
|
|
49
|
+
expected `initializationId`.
|
|
50
|
+
- `invalid_init_bindings` means that the SDK could not apply the data source
|
|
51
|
+
bindings in the host's valid `init` message. The SDK sends this code in
|
|
52
|
+
`initResult.error`.
|
|
53
|
+
- `invalid_init_result_payload` means that the host could not parse the SDK's
|
|
54
|
+
`initResult`. This is a host-local error. The SDK does not send this code.
|
|
55
|
+
- `no_init_result` means that the host did not receive a valid `initResult`
|
|
56
|
+
before its timeout.
|
|
57
|
+
|
|
42
58
|
See [Lifecycle and initialization](./lifecycle.md) for initialization-specific error codes.
|
|
43
59
|
|
|
44
60
|
## Handling errors
|
package/docs/lifecycle.md
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
1
|
# Lifecycle
|
|
2
2
|
|
|
3
|
-
The SDK
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
The SDK and host use a three-message `postMessage` handshake to initialize
|
|
4
|
+
a custom block.
|
|
5
|
+
|
|
6
|
+
This handshake can be completed with one of these APIs:
|
|
7
|
+
|
|
8
|
+
- `<NotionCustomBlock>` for most React blocks. It starts initialization and
|
|
9
|
+
renders `children` after the host state is ready.
|
|
10
|
+
- `useCustomBlockInit()` for React blocks that need to control loading and
|
|
11
|
+
error states.
|
|
12
|
+
- `initCustomBlock()` for framework-neutral code or code that must await
|
|
13
|
+
initialization before it renders.
|
|
14
|
+
|
|
15
|
+
Custom bridge integrations can also handle the `postMessage` wire messages
|
|
16
|
+
directly. This is advanced usage for host or integration authors.
|
|
17
|
+
|
|
18
|
+
## Initialization protocol message
|
|
19
|
+
|
|
20
|
+
The full `postMessage` handshake is:
|
|
21
|
+
|
|
22
|
+
1. The SDK sends `connect` with a new `initializationId` and version
|
|
23
|
+
information.
|
|
24
|
+
2. The host sends `init` with the same `initializationId`, the manifest, data source bindings,
|
|
25
|
+
and additional block and app context.
|
|
26
|
+
3. The SDK validates and applies `init`, renders the initial content, and sends
|
|
27
|
+
`initResult` with the same `initializationId` and the initial block height.
|
|
28
|
+
|
|
29
|
+
Both sides must check that the `initializationId` matches. The SDK ignores an
|
|
30
|
+
`init` with a different ID. It sends `initResult` only after it applies a
|
|
31
|
+
matching `init`.
|
|
32
|
+
|
|
33
|
+
The initialization promise resolves after the SDK applies the host state.
|
|
34
|
+
Await it before rendering code that reads initialized state. The SDK sends
|
|
35
|
+
`initResult` after the initial render.
|
|
36
|
+
|
|
37
|
+
- The promise rejects with `CustomBlockInitializationError` code
|
|
38
|
+
`init_timeout` if the host does not send a usable `init`.
|
|
39
|
+
- If a matching `init` is malformed, the SDK sends `initResult.error` with code
|
|
40
|
+
`invalid_init_payload` and rejects with the same structured error.
|
|
41
|
+
- If the host sends incomplete or invalid data source bindings, the SDK sends
|
|
42
|
+
`initResult.error` with code `invalid_init_bindings` and rejects with the same
|
|
43
|
+
structured error.
|
|
44
|
+
- If the SDK cannot correlate a malformed `init` to the handshake, it sends
|
|
45
|
+
`invalidHostMessage` when safe.
|
|
46
|
+
- After initialization, compatible hosts use `*Changed` messages to push live
|
|
47
|
+
updates.
|
|
48
|
+
- `initCustomBlock()` is idempotent. Later calls return the same promise.
|
|
49
|
+
|
|
50
|
+
## Rendering inside a host
|
|
51
|
+
|
|
52
|
+
The SDK expects the custom block to run inside an iframe. In a top-level
|
|
53
|
+
browser tab, initialization rejects with `NotInIframeError` code
|
|
54
|
+
`not_in_iframe`.
|
|
55
|
+
|
|
56
|
+
`<NotionCustomBlock>` catches this error and renders a standalone preview with
|
|
57
|
+
a warning. This supports local development when no host is available.
|
|
19
58
|
|
|
20
59
|
## Sizing
|
|
21
60
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
To set a shorter height, set `max-height` and `overflow-y` on `#root`:
|
|
61
|
+
Custom blocks resize to fit their content by default. To set a shorter height, set
|
|
62
|
+
`max-height` and `overflow-y` on `#root`:
|
|
25
63
|
|
|
26
64
|
```css
|
|
27
65
|
#root {
|
|
@@ -30,9 +68,25 @@ To set a shorter height, set `max-height` and `overflow-y` on `#root`:
|
|
|
30
68
|
}
|
|
31
69
|
```
|
|
32
70
|
|
|
71
|
+
The Notion app enforces a maximum custom block height of 10,000 pixels.
|
|
72
|
+
|
|
73
|
+
The SDK measures the `#root` element and sends its initial height in
|
|
74
|
+
`initResult.success`. It sends later height changes in `resize` messages. The
|
|
75
|
+
host uses these messages to keep the iframe height in sync.
|
|
76
|
+
|
|
77
|
+
Using `<NotionCustomBlock>` enables this behavior automatically. Use
|
|
78
|
+
`customBlock.autoResize()` for a custom, framework-neutral initialization wrapper.
|
|
79
|
+
|
|
33
80
|
## API
|
|
34
81
|
|
|
35
|
-
Import framework-neutral APIs from `@notionhq/custom-blocks
|
|
82
|
+
Import framework-neutral APIs from `@notionhq/custom-blocks`. Import React
|
|
83
|
+
hooks and components from `@notionhq/custom-blocks/react`.
|
|
84
|
+
|
|
85
|
+
The APIs below require successful initialization unless stated otherwise.
|
|
86
|
+
Initialized-only hooks and `customBlock` getters throw before
|
|
87
|
+
`initCustomBlock()` resolves. Inside `<NotionCustomBlock>`, or after the
|
|
88
|
+
`isLoaded` gate of `useCustomBlockInit`, single-value hooks return non-nullable
|
|
89
|
+
values.
|
|
36
90
|
|
|
37
91
|
### `<NotionCustomBlock>`
|
|
38
92
|
|
package/package.json
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
NOTION_DARK_BACKGROUND_BASE,
|
|
53
53
|
NOTION_LIGHT_BACKGROUND_BASE,
|
|
54
54
|
} from "./appearance.js"
|
|
55
|
+
import { resolveDataSourceQuery } from "./dataSources/query.js"
|
|
55
56
|
import { resolveDataSources } from "./dataSources/resolve.js"
|
|
56
57
|
import { resolvePropertyWriteMapForDataSource } from "./dataSources/resolveProperty.js"
|
|
57
58
|
import {
|
|
@@ -258,6 +259,16 @@ export class SandboxBridge {
|
|
|
258
259
|
parsed.issues,
|
|
259
260
|
)
|
|
260
261
|
const incomingType = readIncomingType(event.data)
|
|
262
|
+
if (
|
|
263
|
+
!this.hasReceivedInit &&
|
|
264
|
+
incomingType === "init" &&
|
|
265
|
+
this.initializationId !== undefined &&
|
|
266
|
+
readInitializationId(event.data) === this.initializationId
|
|
267
|
+
) {
|
|
268
|
+
this.hasReceivedInit = true
|
|
269
|
+
this.sendInitResultError(invalidInitPayloadError(parsed.issues))
|
|
270
|
+
return
|
|
271
|
+
}
|
|
261
272
|
// Loop guard: never NACK a NACK. Excludes both directions —
|
|
262
273
|
// `invalidSandboxMessage` is what the host normally sends, but
|
|
263
274
|
// a buggy host that echoes our own `invalidHostMessage` back
|
|
@@ -383,12 +394,14 @@ export class SandboxBridge {
|
|
|
383
394
|
nextBindings,
|
|
384
395
|
})
|
|
385
396
|
this.latestDataSourceBindings = nextBindings
|
|
386
|
-
// Drop cached query state for
|
|
397
|
+
// Drop cached query state for subscriptions whose key no longer exists.
|
|
387
398
|
const nextKeys = new Set(dataSources.map(s => s.key))
|
|
388
399
|
const prunedState: Record<string, DataSourceQueryState> = {}
|
|
389
|
-
for (const [
|
|
390
|
-
|
|
391
|
-
|
|
400
|
+
for (const [subscriptionId, state] of Object.entries(
|
|
401
|
+
hostState.dataSourceState,
|
|
402
|
+
)) {
|
|
403
|
+
if (nextKeys.has(state.dataSourceKey)) {
|
|
404
|
+
prunedState[subscriptionId] = state
|
|
392
405
|
}
|
|
393
406
|
}
|
|
394
407
|
this.hostState = {
|
|
@@ -466,13 +479,10 @@ export class SandboxBridge {
|
|
|
466
479
|
}
|
|
467
480
|
|
|
468
481
|
case "queryDataSourceResult": {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
)
|
|
472
|
-
if (queryEntry === undefined) {
|
|
482
|
+
const currentState = hostState.dataSourceState[message.subscriptionId]
|
|
483
|
+
if (currentState === undefined) {
|
|
473
484
|
return
|
|
474
485
|
}
|
|
475
|
-
const [key, currentState] = queryEntry
|
|
476
486
|
const queryResult =
|
|
477
487
|
message.status === "error"
|
|
478
488
|
? {
|
|
@@ -489,13 +499,14 @@ export class SandboxBridge {
|
|
|
489
499
|
...hostState,
|
|
490
500
|
dataSourceState: {
|
|
491
501
|
...hostState.dataSourceState,
|
|
492
|
-
[
|
|
502
|
+
[message.subscriptionId]: {
|
|
503
|
+
dataSourceKey: currentState.dataSourceKey,
|
|
493
504
|
items: queryResult.items,
|
|
494
505
|
isLoading: false,
|
|
495
506
|
hasMore: queryResult.hasMore,
|
|
496
507
|
error: queryResult.error,
|
|
497
|
-
subscriptionId: message.subscriptionId,
|
|
498
508
|
latestLimit: currentState.latestLimit,
|
|
509
|
+
latestQueryIdentity: currentState.latestQueryIdentity,
|
|
499
510
|
},
|
|
500
511
|
},
|
|
501
512
|
}
|
|
@@ -548,6 +559,25 @@ export class SandboxBridge {
|
|
|
548
559
|
this.flushPendingOutboundMessages()
|
|
549
560
|
}
|
|
550
561
|
|
|
562
|
+
private sendInitResultError(error: CustomBlockInitResultErrorInfo) {
|
|
563
|
+
if (this.hasSentInitResult || this.initializationId === undefined) {
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
const result: InitResultMessage = {
|
|
567
|
+
type: "initResult",
|
|
568
|
+
initializationId: this.initializationId,
|
|
569
|
+
status: "error",
|
|
570
|
+
error,
|
|
571
|
+
}
|
|
572
|
+
this.hasSentInitResult = true
|
|
573
|
+
this.postToHost(result)
|
|
574
|
+
if (this.rejectInit) {
|
|
575
|
+
this.rejectInit(new CustomBlockInitializationError(error))
|
|
576
|
+
this.resolveInit = undefined
|
|
577
|
+
this.rejectInit = undefined
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
551
581
|
private applyInit(message: InitMessage, postResult: boolean) {
|
|
552
582
|
if (postResult) {
|
|
553
583
|
this.isMockState = false
|
|
@@ -581,18 +611,7 @@ export class SandboxBridge {
|
|
|
581
611
|
: resolveMockDataSources(this.latestDataSourceBindings)
|
|
582
612
|
const bindingError = getInitBindingError(message.manifest, dataSources)
|
|
583
613
|
if (postResult && bindingError !== undefined) {
|
|
584
|
-
|
|
585
|
-
type: "initResult",
|
|
586
|
-
initializationId: message.initializationId,
|
|
587
|
-
status: "error",
|
|
588
|
-
error: bindingError,
|
|
589
|
-
}
|
|
590
|
-
this.postToHost(result)
|
|
591
|
-
if (this.rejectInit) {
|
|
592
|
-
this.rejectInit(new CustomBlockInitializationError(bindingError))
|
|
593
|
-
this.resolveInit = undefined
|
|
594
|
-
this.rejectInit = undefined
|
|
595
|
-
}
|
|
614
|
+
this.sendInitResultError(bindingError)
|
|
596
615
|
return
|
|
597
616
|
}
|
|
598
617
|
this.hostState = {
|
|
@@ -654,7 +673,15 @@ export class SandboxBridge {
|
|
|
654
673
|
}
|
|
655
674
|
}
|
|
656
675
|
|
|
657
|
-
|
|
676
|
+
createDataSourceSubscriptionId(): string {
|
|
677
|
+
return `data-source:${globalThis.crypto.randomUUID()}`
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
queryDataSource(
|
|
681
|
+
subscriptionId: string,
|
|
682
|
+
key: string,
|
|
683
|
+
options: UseDataSourceOptions = {},
|
|
684
|
+
) {
|
|
658
685
|
if (this.hostState.status !== "initialized") {
|
|
659
686
|
return
|
|
660
687
|
}
|
|
@@ -662,10 +689,13 @@ export class SandboxBridge {
|
|
|
662
689
|
const dataSource = this.hostState.dataSources.find(
|
|
663
690
|
entry => entry.key === key,
|
|
664
691
|
)
|
|
692
|
+
const subscriptionState = this.hostState.dataSourceState[subscriptionId]
|
|
665
693
|
const currentState =
|
|
666
|
-
|
|
694
|
+
subscriptionState?.dataSourceKey === key
|
|
695
|
+
? subscriptionState
|
|
696
|
+
: createEmptyDataSourceQueryState(key)
|
|
667
697
|
if (dataSource === undefined) {
|
|
668
|
-
this.setDataSourceQueryError(
|
|
698
|
+
this.setDataSourceQueryError(subscriptionId, currentState, {
|
|
669
699
|
code: "unknown_data_source_key",
|
|
670
700
|
message: `Unknown data source key "${key}". Known keys: [${this.hostState.dataSources.map(entry => entry.key).join(", ")}].`,
|
|
671
701
|
isRetryable: false,
|
|
@@ -673,7 +703,7 @@ export class SandboxBridge {
|
|
|
673
703
|
return
|
|
674
704
|
}
|
|
675
705
|
if (dataSource.collectionPointer === undefined) {
|
|
676
|
-
this.setDataSourceQueryError(
|
|
706
|
+
this.setDataSourceQueryError(subscriptionId, currentState, {
|
|
677
707
|
code: "unmapped_data_source",
|
|
678
708
|
message: `Data source "${key}" has not been mapped to a database yet.`,
|
|
679
709
|
isRetryable: false,
|
|
@@ -681,15 +711,24 @@ export class SandboxBridge {
|
|
|
681
711
|
return
|
|
682
712
|
}
|
|
683
713
|
|
|
684
|
-
const
|
|
685
|
-
|
|
714
|
+
const resolvedQuery = resolveDataSourceQuery({
|
|
715
|
+
dataSources: this.hostState.dataSources,
|
|
686
716
|
key,
|
|
717
|
+
options,
|
|
687
718
|
})
|
|
719
|
+
if (resolvedQuery.status === "error") {
|
|
720
|
+
this.setDataSourceQueryError(subscriptionId, currentState, {
|
|
721
|
+
code: "invalid_data_source_query",
|
|
722
|
+
message: resolvedQuery.error,
|
|
723
|
+
isRetryable: false,
|
|
724
|
+
})
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
const query = resolvedQuery.query
|
|
688
728
|
|
|
689
729
|
if (
|
|
690
730
|
currentState.isLoading &&
|
|
691
|
-
currentState.
|
|
692
|
-
currentState.latestLimit === limit
|
|
731
|
+
currentState.latestQueryIdentity === query.identity
|
|
693
732
|
) {
|
|
694
733
|
return
|
|
695
734
|
}
|
|
@@ -698,12 +737,13 @@ export class SandboxBridge {
|
|
|
698
737
|
...this.hostState,
|
|
699
738
|
dataSourceState: {
|
|
700
739
|
...this.hostState.dataSourceState,
|
|
701
|
-
[
|
|
740
|
+
[subscriptionId]: {
|
|
702
741
|
...currentState,
|
|
742
|
+
dataSourceKey: key,
|
|
703
743
|
isLoading: true,
|
|
704
744
|
error: undefined,
|
|
705
|
-
|
|
706
|
-
|
|
745
|
+
latestLimit: query.limit,
|
|
746
|
+
latestQueryIdentity: query.identity,
|
|
707
747
|
},
|
|
708
748
|
},
|
|
709
749
|
}
|
|
@@ -712,14 +752,15 @@ export class SandboxBridge {
|
|
|
712
752
|
const outbound: QueryDataSourceMessage = {
|
|
713
753
|
type: "queryDataSource",
|
|
714
754
|
subscriptionId,
|
|
715
|
-
dataSourceId:
|
|
716
|
-
limit,
|
|
755
|
+
dataSourceId: query.dataSourceId,
|
|
756
|
+
limit: query.limit,
|
|
757
|
+
...(query.filter !== undefined ? { filter: query.filter } : {}),
|
|
717
758
|
}
|
|
718
759
|
this.postToHost(outbound)
|
|
719
760
|
}
|
|
720
761
|
|
|
721
762
|
private setDataSourceQueryError(
|
|
722
|
-
|
|
763
|
+
subscriptionId: string,
|
|
723
764
|
currentState: DataSourceQueryState,
|
|
724
765
|
error: CustomBlockQueryDataSourceErrorInfo,
|
|
725
766
|
) {
|
|
@@ -730,18 +771,33 @@ export class SandboxBridge {
|
|
|
730
771
|
...this.hostState,
|
|
731
772
|
dataSourceState: {
|
|
732
773
|
...this.hostState.dataSourceState,
|
|
733
|
-
[
|
|
774
|
+
[subscriptionId]: {
|
|
734
775
|
...currentState,
|
|
735
776
|
isLoading: false,
|
|
736
777
|
error,
|
|
737
|
-
subscriptionId: undefined,
|
|
738
778
|
latestLimit: undefined,
|
|
779
|
+
latestQueryIdentity: undefined,
|
|
739
780
|
},
|
|
740
781
|
},
|
|
741
782
|
}
|
|
742
783
|
this.notify()
|
|
743
784
|
}
|
|
744
785
|
|
|
786
|
+
releaseDataSourceSubscription(subscriptionId: string) {
|
|
787
|
+
if (
|
|
788
|
+
this.hostState.status !== "initialized" ||
|
|
789
|
+
this.hostState.dataSourceState[subscriptionId] === undefined
|
|
790
|
+
) {
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
const { [subscriptionId]: _, ...dataSourceState } =
|
|
794
|
+
this.hostState.dataSourceState
|
|
795
|
+
this.hostState = {
|
|
796
|
+
...this.hostState,
|
|
797
|
+
dataSourceState,
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
745
801
|
postResize(height: number) {
|
|
746
802
|
if (typeof window === "undefined") {
|
|
747
803
|
return
|
|
@@ -1036,34 +1092,26 @@ function reuseDataSourcesForUnchangedBindings(args: {
|
|
|
1036
1092
|
})
|
|
1037
1093
|
}
|
|
1038
1094
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (limit === undefined) {
|
|
1047
|
-
return DEFAULT_DATA_SOURCE_QUERY_LIMIT
|
|
1048
|
-
}
|
|
1049
|
-
if (!Number.isFinite(limit) || !Number.isInteger(limit) || limit < 1) {
|
|
1050
|
-
console.warn(
|
|
1051
|
-
`[custom-blocks-sdk] useDataSource limit must be a positive integer; using ${DEFAULT_DATA_SOURCE_QUERY_LIMIT}.`,
|
|
1052
|
-
)
|
|
1053
|
-
return DEFAULT_DATA_SOURCE_QUERY_LIMIT
|
|
1054
|
-
}
|
|
1055
|
-
if (limit > MAX_DATA_SOURCE_QUERY_LIMIT) {
|
|
1056
|
-
console.warn(
|
|
1057
|
-
`[custom-blocks-sdk] useDataSource limit is capped at ${MAX_DATA_SOURCE_QUERY_LIMIT}.`,
|
|
1058
|
-
)
|
|
1059
|
-
return MAX_DATA_SOURCE_QUERY_LIMIT
|
|
1095
|
+
function invalidInitPayloadError(
|
|
1096
|
+
issues: readonly v.BaseIssue<unknown>[],
|
|
1097
|
+
): CustomBlockInitResultErrorInfo {
|
|
1098
|
+
return {
|
|
1099
|
+
code: "invalid_init_payload",
|
|
1100
|
+
message: formatInvalidHostReason("init", issues),
|
|
1101
|
+
isRetryable: false,
|
|
1060
1102
|
}
|
|
1061
|
-
return limit
|
|
1062
1103
|
}
|
|
1063
1104
|
|
|
1064
|
-
function
|
|
1065
|
-
|
|
1066
|
-
|
|
1105
|
+
function readInitializationId(data: unknown): string | undefined {
|
|
1106
|
+
if (
|
|
1107
|
+
typeof data === "object" &&
|
|
1108
|
+
data !== null &&
|
|
1109
|
+
"initializationId" in data &&
|
|
1110
|
+
typeof data.initializationId === "string"
|
|
1111
|
+
) {
|
|
1112
|
+
return data.initializationId
|
|
1113
|
+
}
|
|
1114
|
+
return undefined
|
|
1067
1115
|
}
|
|
1068
1116
|
|
|
1069
1117
|
function getInitBindingError(
|