@notionhq/custom-blocks-host 0.1.24 → 0.1.26
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 +13 -8
- package/dist/createCustomBlockHost.d.ts +8 -6
- package/dist/createCustomBlockHost.d.ts.map +1 -1
- package/dist/createCustomBlockHost.js +31 -18
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +6 -0
- package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +1 -0
- package/dist/lifecycle/normalizeHostToSandboxMessage.js +38 -0
- package/dist/protocol/dataSources/dataSource.d.ts +61 -6
- package/dist/protocol/dataSources/dataSource.js +35 -6
- package/dist/protocol/dataSources/propertySchema.d.ts +2 -0
- package/dist/protocol/dataSources/propertySchema.js +7 -0
- package/dist/protocol/messages/createPageResult.d.ts +1 -1
- package/dist/protocol/messages/getPage.d.ts +1 -1
- package/dist/protocol/messages/hostToSandbox.d.ts +10 -4
- package/dist/protocol/messages/init.d.ts +5 -2
- package/dist/protocol/messages/openPage.d.ts +1 -1
- package/dist/protocol/messages/pageChanged.d.ts +5 -2
- package/dist/protocol/messages/queryDataSource.d.ts +586 -485
- package/dist/protocol/messages/queryDataSource.js +61 -43
- package/dist/protocol/messages/queryDataSourceResult.d.ts +1 -1
- package/dist/protocol/messages/sandboxToHost.d.ts +212 -178
- package/dist/protocol/messages/updatePageResult.d.ts +1 -1
- package/dist/protocol/pages/page.d.ts +5 -2
- package/dist/protocol/pages/page.js +15 -9
- package/dist/protocol/protocolVersion.d.ts +1 -1
- package/dist/protocol/protocolVersion.js +1 -1
- package/dist/queries/validateQueryDataSourceFilter.d.ts +1 -1
- package/dist/queries/validateQueryDataSourceFilter.d.ts.map +1 -1
- package/dist/queries/validateQueryDataSourceFilter.js +11 -6
- package/dist/queries/validateQueryDataSourceSorts.d.ts +1 -1
- package/dist/queries/validateQueryDataSourceSorts.d.ts.map +1 -1
- package/dist/queries/validateQueryDataSourceSorts.js +3 -5
- package/package.json +1 -1
- package/src/createCustomBlockHost.ts +54 -31
- package/src/lifecycle/normalizeHostToSandboxMessage.ts +46 -0
- package/src/queries/validateQueryDataSourceFilter.ts +12 -7
- package/src/queries/validateQueryDataSourceSorts.ts +4 -10
|
@@ -8,7 +8,10 @@ import * as v from "valibot";
|
|
|
8
8
|
/**
|
|
9
9
|
* The custom block's nearest enclosing page ancestor, carried in `init.page` and `pageChanged`.
|
|
10
10
|
*/
|
|
11
|
-
export declare const customBlockPageSchema: v.ObjectSchema<{
|
|
11
|
+
export declare const customBlockPageSchema: v.VariantSchema<"type", [v.ObjectSchema<{
|
|
12
|
+
readonly type: v.LiteralSchema<"unavailable", undefined>;
|
|
13
|
+
}, undefined>, v.ObjectSchema<{
|
|
14
|
+
readonly type: v.LiteralSchema<"page", undefined>;
|
|
12
15
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionPageId">]>;
|
|
13
16
|
/**
|
|
14
17
|
* The containing page's own parent. It lets a block distinguish a freestanding page from a
|
|
@@ -33,7 +36,7 @@ export declare const customBlockPageSchema: v.ObjectSchema<{
|
|
|
33
36
|
}, undefined>, v.ObjectSchema<{
|
|
34
37
|
readonly type: v.LiteralSchema<"unavailable", undefined>;
|
|
35
38
|
}, undefined>], undefined>;
|
|
36
|
-
}, undefined>;
|
|
39
|
+
}, undefined>], undefined>;
|
|
37
40
|
export type CustomBlockPage = v.InferOutput<typeof customBlockPageSchema>;
|
|
38
41
|
export declare const notionEmojiIconSchema: v.ObjectSchema<{
|
|
39
42
|
readonly type: v.LiteralSchema<"emoji", undefined>;
|
|
@@ -10,15 +10,21 @@ import { notionParentSchema } from "../parent.js";
|
|
|
10
10
|
/**
|
|
11
11
|
* The custom block's nearest enclosing page ancestor, carried in `init.page` and `pageChanged`.
|
|
12
12
|
*/
|
|
13
|
-
export const customBlockPageSchema = v.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
export const customBlockPageSchema = v.variant("type", [
|
|
14
|
+
/** The viewer cannot access the containing page; its ID and parent are not exposed. */
|
|
15
|
+
v.object({ type: v.literal("unavailable") }),
|
|
16
|
+
/** The viewer can access the containing page; includes its ID and parent location. */
|
|
17
|
+
v.object({
|
|
18
|
+
type: v.literal("page"),
|
|
19
|
+
id: notionPageIdSchema,
|
|
20
|
+
/**
|
|
21
|
+
* The containing page's own parent. It lets a block distinguish a freestanding page from a
|
|
22
|
+
* database row without a round trip. Not to be confused with the top-level `parent` runtime
|
|
23
|
+
* value, which is the custom block's own parent.
|
|
24
|
+
*/
|
|
25
|
+
parent: notionParentSchema,
|
|
26
|
+
}),
|
|
27
|
+
]);
|
|
22
28
|
export const notionEmojiIconSchema = v.object({
|
|
23
29
|
type: v.literal("emoji"),
|
|
24
30
|
emoji: v.string(),
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Increase this value for breaking wire changes. Hosts can continue to accept
|
|
4
4
|
* older versions, so this value does not set the minimum or maximum they accept.
|
|
5
5
|
*/
|
|
6
|
-
export declare const LATEST_BRIDGE_PROTOCOL_VERSION =
|
|
6
|
+
export declare const LATEST_BRIDGE_PROTOCOL_VERSION = 5;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NotionCollectionSchema } from "../protocol/dataSources/dataSource.js";
|
|
2
2
|
import type { ResolvedCustomBlockDataSourceFilter } from "../protocol/messages/queryDataSource.js";
|
|
3
3
|
import type { CustomBlockQueryDataSourceErrorInfo } from "../protocol/messages/queryDataSourceResult.js";
|
|
4
|
-
export declare function validateQueryDataSourceFilter(filter: ResolvedCustomBlockDataSourceFilter | undefined, collectionSchema: NotionCollectionSchema
|
|
4
|
+
export declare function validateQueryDataSourceFilter(filter: ResolvedCustomBlockDataSourceFilter | undefined, collectionSchema: NotionCollectionSchema): CustomBlockQueryDataSourceErrorInfo | undefined;
|
|
5
5
|
//# sourceMappingURL=validateQueryDataSourceFilter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateQueryDataSourceFilter.d.ts","sourceRoot":"","sources":["../../src/queries/validateQueryDataSourceFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4DAA4D,CAAA;AACxG,OAAO,KAAK,EAGX,mCAAmC,EAEnC,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,oEAAoE,CAAA;AAO7H,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,mCAAmC,GAAG,SAAS,EACvD,gBAAgB,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"validateQueryDataSourceFilter.d.ts","sourceRoot":"","sources":["../../src/queries/validateQueryDataSourceFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4DAA4D,CAAA;AACxG,OAAO,KAAK,EAGX,mCAAmC,EAEnC,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,oEAAoE,CAAA;AAO7H,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,mCAAmC,GAAG,SAAS,EACvD,gBAAgB,EAAE,sBAAsB,GACtC,mCAAmC,GAAG,SAAS,CAajD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unreachable } from "../utils.js";
|
|
2
2
|
export function validateQueryDataSourceFilter(filter, collectionSchema) {
|
|
3
|
-
if (filter === undefined
|
|
3
|
+
if (filter === undefined) {
|
|
4
4
|
return undefined;
|
|
5
5
|
}
|
|
6
6
|
const propertyFilters = "and" in filter ? filter.and : [filter];
|
|
@@ -13,14 +13,19 @@ export function validateQueryDataSourceFilter(filter, collectionSchema) {
|
|
|
13
13
|
return undefined;
|
|
14
14
|
}
|
|
15
15
|
function validatePropertyFilter(filter, collectionSchema) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (!Object.hasOwn(collectionSchema.propertiesById, filter.propertyId)) {
|
|
17
|
+
return {
|
|
18
|
+
code: "invalid_data_source_query_filter",
|
|
19
|
+
message: `Data source query filter property ID "${filter.propertyId}" is not defined in the bound data source schema.`,
|
|
20
|
+
isRetryable: false,
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
|
-
|
|
23
|
+
const propertySchema = collectionSchema.propertiesById[filter.propertyId];
|
|
24
|
+
const branch = Object.keys(filter).find(key => key !== "propertyId");
|
|
25
|
+
if (branch !== propertySchema.type) {
|
|
21
26
|
return {
|
|
22
27
|
code: "invalid_data_source_query_filter",
|
|
23
|
-
message: `Data source query filter branch "
|
|
28
|
+
message: `Data source query filter branch "${branch}" does not match property type "${propertySchema.type}".`,
|
|
24
29
|
isRetryable: false,
|
|
25
30
|
};
|
|
26
31
|
}
|
|
@@ -4,5 +4,5 @@ import type { CustomBlockQueryDataSourceErrorInfo } from "../protocol/messages/q
|
|
|
4
4
|
/**
|
|
5
5
|
* Ensures the sorts of a data source query are valid.
|
|
6
6
|
*/
|
|
7
|
-
export declare function validateQueryDataSourceSorts(sorts: ResolvedCustomBlockDataSourceSort[] | undefined, collectionSchema: NotionCollectionSchema
|
|
7
|
+
export declare function validateQueryDataSourceSorts(sorts: ResolvedCustomBlockDataSourceSort[] | undefined, collectionSchema: NotionCollectionSchema): CustomBlockQueryDataSourceErrorInfo | undefined;
|
|
8
8
|
//# sourceMappingURL=validateQueryDataSourceSorts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateQueryDataSourceSorts.d.ts","sourceRoot":"","sources":["../../src/queries/validateQueryDataSourceSorts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4DAA4D,CAAA;AAExG,OAAO,EAEN,KAAK,iCAAiC,EACtC,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,oEAAoE,CAAA;AAM7H;;GAEG;AACH,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,iCAAiC,EAAE,GAAG,SAAS,EACtD,gBAAgB,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"validateQueryDataSourceSorts.d.ts","sourceRoot":"","sources":["../../src/queries/validateQueryDataSourceSorts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4DAA4D,CAAA;AAExG,OAAO,EAEN,KAAK,iCAAiC,EACtC,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,oEAAoE,CAAA;AAM7H;;GAEG;AACH,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,iCAAiC,EAAE,GAAG,SAAS,EACtD,gBAAgB,EAAE,sBAAsB,GACtC,mCAAmC,GAAG,SAAS,CAyCjD"}
|
|
@@ -25,17 +25,15 @@ export function validateQueryDataSourceSorts(sorts, collectionSchema) {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
seenPropertyIds.add(sort.propertyId);
|
|
28
|
-
if (collectionSchema
|
|
29
|
-
!Object.hasOwn(collectionSchema.propertiesById, sort.propertyId)) {
|
|
28
|
+
if (!Object.hasOwn(collectionSchema.propertiesById, sort.propertyId)) {
|
|
30
29
|
return {
|
|
31
30
|
code: "invalid_data_source_query_sort",
|
|
32
31
|
message: `Data source query sort property ID "${sort.propertyId}" is not defined in the bound data source schema.`,
|
|
33
32
|
isRetryable: false,
|
|
34
33
|
};
|
|
35
34
|
}
|
|
36
|
-
const propertySchema = collectionSchema
|
|
37
|
-
if (propertySchema
|
|
38
|
-
!supportedDataSourceSortPropertyTypes.has(propertySchema.type)) {
|
|
35
|
+
const propertySchema = collectionSchema.propertiesById[sort.propertyId];
|
|
36
|
+
if (!supportedDataSourceSortPropertyTypes.has(propertySchema.type)) {
|
|
39
37
|
return {
|
|
40
38
|
code: "invalid_data_source_query_sort",
|
|
41
39
|
message: `Data source query sorts do not yet support property type "${propertySchema.type}".`,
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_CONTRAST_MODE,
|
|
3
3
|
type NotionContrastMode,
|
|
4
4
|
} from "@notionhq/custom-blocks-protocol/contrast.js"
|
|
5
|
+
import type { NotionCollectionSchema } from "@notionhq/custom-blocks-protocol/dataSources/dataSource.js"
|
|
5
6
|
import type { NotionDataSourceId } from "@notionhq/custom-blocks-protocol/ids.js"
|
|
6
7
|
import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
|
|
7
8
|
import type { ContrastModeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/contrastModeChanged.js"
|
|
@@ -29,16 +30,21 @@ import type {
|
|
|
29
30
|
import type { PageChangedMessage } from "@notionhq/custom-blocks-protocol/messages/pageChanged.js"
|
|
30
31
|
import type { ParentChangedMessage } from "@notionhq/custom-blocks-protocol/messages/parentChanged.js"
|
|
31
32
|
import type { QueryDataSourceMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSource.js"
|
|
32
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
CustomBlockQueryDataSourceErrorInfo,
|
|
35
|
+
QueryDataSourceResultMessage,
|
|
36
|
+
} from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
|
|
33
37
|
import type { ThemeChangedMessage } from "@notionhq/custom-blocks-protocol/messages/themeChanged.js"
|
|
34
38
|
import type { UpdatePageMessage } from "@notionhq/custom-blocks-protocol/messages/updatePage.js"
|
|
35
39
|
import type { UpdatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/updatePageResult.js"
|
|
36
40
|
import type { CustomBlockPage } from "@notionhq/custom-blocks-protocol/pages/page.js"
|
|
37
41
|
import type { NotionParent } from "@notionhq/custom-blocks-protocol/parent.js"
|
|
42
|
+
import { LATEST_BRIDGE_PROTOCOL_VERSION } from "@notionhq/custom-blocks-protocol/protocolVersion.js"
|
|
38
43
|
import type { NotionTheme } from "@notionhq/custom-blocks-protocol/theme.js"
|
|
39
44
|
import type { NotionUser } from "@notionhq/custom-blocks-protocol/users/user.js"
|
|
40
45
|
import { initErrorForFailureReason } from "./lifecycle/initErrors.js"
|
|
41
46
|
import { createInitializationController } from "./lifecycle/initializationController.js"
|
|
47
|
+
import { normalizeHostToSandboxMessage } from "./lifecycle/normalizeHostToSandboxMessage.js"
|
|
42
48
|
import type {
|
|
43
49
|
CustomBlockHostDataSourcesPayload,
|
|
44
50
|
CustomBlockHostInitialState,
|
|
@@ -62,16 +68,19 @@ export type CustomBlockHostOptions = {
|
|
|
62
68
|
iframe: HTMLIFrameElement
|
|
63
69
|
sandboxOrigin: string
|
|
64
70
|
/**
|
|
65
|
-
* Minimum sandbox protocol version to accept. Defaults to the host runtime
|
|
66
|
-
*
|
|
67
|
-
*
|
|
71
|
+
* Minimum sandbox protocol version to accept. Defaults to the host runtime minimum. Higher
|
|
72
|
+
* values retire older SDKs; lower values cannot enable protocol versions the runtime no longer
|
|
73
|
+
* supports.
|
|
68
74
|
*/
|
|
69
75
|
minBridgeProtocolVersion?: number
|
|
70
76
|
noConnectTimeoutMs?: number
|
|
71
77
|
noInitResultTimeoutMs?: number
|
|
72
78
|
initialState: CustomBlockHostInitialState
|
|
73
79
|
handlers: CustomBlockHostHandlers
|
|
74
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Called as soon as the `manifest` is available after `connect`, before `onInitialized` and
|
|
82
|
+
* binding validation.
|
|
83
|
+
*/
|
|
75
84
|
onManifest?: (manifest: CustomBlockManifest) => void
|
|
76
85
|
/** Callback fired when the initialization handshake with the sandbox completed successfully. */
|
|
77
86
|
onInitialized?: (state: {
|
|
@@ -83,8 +92,7 @@ export type CustomBlockHostOptions = {
|
|
|
83
92
|
error: CustomBlockHostInitializationFailure,
|
|
84
93
|
) => void
|
|
85
94
|
/**
|
|
86
|
-
* Callback fired when the current host and iframe must restart.
|
|
87
|
-
* Recreate both to continue.
|
|
95
|
+
* Callback fired when the current host and iframe must restart. Recreate both to continue.
|
|
88
96
|
*/
|
|
89
97
|
onRestartRequired?: () => void
|
|
90
98
|
onLog?: (direction: CustomBlockHostLogDirection, payload: unknown) => void
|
|
@@ -119,8 +127,8 @@ export function createCustomBlockHost(
|
|
|
119
127
|
noConnectTimeoutMs,
|
|
120
128
|
noInitResultTimeoutMs,
|
|
121
129
|
} = options
|
|
122
|
-
// Callers can retire older SDKs, but cannot restore support for protocols
|
|
123
|
-
//
|
|
130
|
+
// Callers can retire older SDKs, but cannot restore support for protocols the runtime no longer
|
|
131
|
+
// implements. Omission uses the runtime minimum.
|
|
124
132
|
const minBridgeProtocolVersion = Math.max(
|
|
125
133
|
configuredMinBridgeProtocolVersion ??
|
|
126
134
|
MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION,
|
|
@@ -133,6 +141,7 @@ export function createCustomBlockHost(
|
|
|
133
141
|
let page = initialState.page
|
|
134
142
|
let dataSources = initialState.dataSources
|
|
135
143
|
let activeDataSources: CustomBlockHostDataSourcesPayload | undefined
|
|
144
|
+
let sandboxBridgeProtocolVersion = LATEST_BRIDGE_PROTOCOL_VERSION
|
|
136
145
|
let manifest = initialState.manifest
|
|
137
146
|
let currentUser = initialState.currentUser
|
|
138
147
|
let pendingThemeChange = false
|
|
@@ -148,8 +157,12 @@ export function createCustomBlockHost(
|
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
function post(message: HostToSandboxMessage) {
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
const sandboxMessage = normalizeHostToSandboxMessage(
|
|
161
|
+
message,
|
|
162
|
+
sandboxBridgeProtocolVersion,
|
|
163
|
+
)
|
|
164
|
+
emit("out", sandboxMessage)
|
|
165
|
+
iframe.contentWindow?.postMessage(sandboxMessage, sandboxOrigin)
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
function postQueryDataSourceResult(args: {
|
|
@@ -182,28 +195,12 @@ export function createCustomBlockHost(
|
|
|
182
195
|
activeDataSources,
|
|
183
196
|
message.dataSourceId,
|
|
184
197
|
)
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
collectionSchema,
|
|
188
|
-
)
|
|
189
|
-
if (filterError !== undefined) {
|
|
198
|
+
const queryError = getQueryValidationError(message, collectionSchema)
|
|
199
|
+
if (queryError !== undefined) {
|
|
190
200
|
if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
|
|
191
201
|
postQueryDataSourceResult({
|
|
192
202
|
subscriptionId: message.subscriptionId,
|
|
193
|
-
response: { status: "error", error:
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
const sortError = validateQueryDataSourceSorts(
|
|
199
|
-
message.sorts,
|
|
200
|
-
collectionSchema,
|
|
201
|
-
)
|
|
202
|
-
if (sortError !== undefined) {
|
|
203
|
-
if (querySubscriptions.isCurrent(message.subscriptionId, token)) {
|
|
204
|
-
postQueryDataSourceResult({
|
|
205
|
-
subscriptionId: message.subscriptionId,
|
|
206
|
-
response: { status: "error", error: sortError },
|
|
203
|
+
response: { status: "error", error: queryError },
|
|
207
204
|
})
|
|
208
205
|
}
|
|
209
206
|
return
|
|
@@ -321,7 +318,10 @@ export function createCustomBlockHost(
|
|
|
321
318
|
if (pendingDataSourcesChange !== undefined) {
|
|
322
319
|
const nextDataSources = pendingDataSourcesChange
|
|
323
320
|
pendingDataSourcesChange = undefined
|
|
324
|
-
post({
|
|
321
|
+
post({
|
|
322
|
+
type: "dataSourcesChanged",
|
|
323
|
+
dataSources: nextDataSources,
|
|
324
|
+
})
|
|
325
325
|
}
|
|
326
326
|
if (pendingCurrentUserChange) {
|
|
327
327
|
pendingCurrentUserChange = false
|
|
@@ -339,6 +339,7 @@ export function createCustomBlockHost(
|
|
|
339
339
|
currentUser,
|
|
340
340
|
}),
|
|
341
341
|
prepareInit(connect) {
|
|
342
|
+
sandboxBridgeProtocolVersion = connect.bridgeProtocolVersion
|
|
342
343
|
manifest = connect.manifest ?? initialState.manifest
|
|
343
344
|
if (manifest === undefined) {
|
|
344
345
|
return {
|
|
@@ -553,3 +554,25 @@ function getBindingErrorCode(
|
|
|
553
554
|
}
|
|
554
555
|
return undefined
|
|
555
556
|
}
|
|
557
|
+
|
|
558
|
+
function getQueryValidationError(
|
|
559
|
+
message: QueryDataSourceMessage,
|
|
560
|
+
collectionSchema: NotionCollectionSchema | undefined,
|
|
561
|
+
): CustomBlockQueryDataSourceErrorInfo | undefined {
|
|
562
|
+
// The host can omit the database schema. Queries without filters or sorts need no schema validation.
|
|
563
|
+
if (collectionSchema === undefined) {
|
|
564
|
+
if (message.filter === undefined && (message.sorts?.length ?? 0) === 0) {
|
|
565
|
+
return undefined
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
code: "data_source_schema_unavailable",
|
|
569
|
+
message:
|
|
570
|
+
"You cannot query this data source with filters or sorts because its schema is unavailable.",
|
|
571
|
+
isRetryable: false,
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return (
|
|
575
|
+
validateQueryDataSourceFilter(message.filter, collectionSchema) ??
|
|
576
|
+
validateQueryDataSourceSorts(message.sorts, collectionSchema)
|
|
577
|
+
)
|
|
578
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NOTION_BUILTIN_PROPERTY_SCHEMAS } from "@notionhq/custom-blocks-protocol/dataSources/propertySchema.js"
|
|
2
|
+
import type { HostToSandboxMessage } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes an outgoing message for the bridge protocol version requested by the sandbox.
|
|
6
|
+
*/
|
|
7
|
+
// TODO(custom-blocks): Remove this function when MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION
|
|
8
|
+
// reaches 5.
|
|
9
|
+
export function normalizeHostToSandboxMessage(
|
|
10
|
+
message: HostToSandboxMessage,
|
|
11
|
+
bridgeProtocolVersion: number,
|
|
12
|
+
): HostToSandboxMessage {
|
|
13
|
+
// The latest SDK version does not need any normalization.
|
|
14
|
+
if (bridgeProtocolVersion >= 5) {
|
|
15
|
+
return message
|
|
16
|
+
}
|
|
17
|
+
const containsDataSources =
|
|
18
|
+
(message.type === "init" && message.status === "success") ||
|
|
19
|
+
message.type === "dataSourcesChanged"
|
|
20
|
+
if (!containsDataSources) {
|
|
21
|
+
return message
|
|
22
|
+
}
|
|
23
|
+
// Protocol 5 excludes synthetic IDs such as created_time from collectionSchema.propertiesById.
|
|
24
|
+
// Older SDKs derive read schemas from that map. Restore those entries in outgoing copies so
|
|
25
|
+
// host query validation still uses the actual database schema.
|
|
26
|
+
return {
|
|
27
|
+
...message,
|
|
28
|
+
dataSources: {
|
|
29
|
+
bindings: Object.fromEntries(
|
|
30
|
+
Object.entries(message.dataSources.bindings).map(([key, binding]) => [
|
|
31
|
+
key,
|
|
32
|
+
{
|
|
33
|
+
...binding,
|
|
34
|
+
collectionSchema: {
|
|
35
|
+
...binding.collectionSchema,
|
|
36
|
+
propertiesById: {
|
|
37
|
+
...binding.collectionSchema?.propertiesById,
|
|
38
|
+
...NOTION_BUILTIN_PROPERTY_SCHEMAS,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
]),
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -14,9 +14,9 @@ type OptionFilterOperator =
|
|
|
14
14
|
|
|
15
15
|
export function validateQueryDataSourceFilter(
|
|
16
16
|
filter: ResolvedCustomBlockDataSourceFilter | undefined,
|
|
17
|
-
collectionSchema: NotionCollectionSchema
|
|
17
|
+
collectionSchema: NotionCollectionSchema,
|
|
18
18
|
): CustomBlockQueryDataSourceErrorInfo | undefined {
|
|
19
|
-
if (filter === undefined
|
|
19
|
+
if (filter === undefined) {
|
|
20
20
|
return undefined
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -34,15 +34,20 @@ function validatePropertyFilter(
|
|
|
34
34
|
filter: ResolvedCustomBlockDataSourcePropertyFilter,
|
|
35
35
|
collectionSchema: NotionCollectionSchema,
|
|
36
36
|
): CustomBlockQueryDataSourceErrorInfo | undefined {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if (!Object.hasOwn(collectionSchema.propertiesById, filter.propertyId)) {
|
|
38
|
+
return {
|
|
39
|
+
code: "invalid_data_source_query_filter",
|
|
40
|
+
message: `Data source query filter property ID "${filter.propertyId}" is not defined in the bound data source schema.`,
|
|
41
|
+
isRetryable: false,
|
|
42
|
+
}
|
|
40
43
|
}
|
|
44
|
+
const propertySchema = collectionSchema.propertiesById[filter.propertyId]
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
const branch = Object.keys(filter).find(key => key !== "propertyId")
|
|
47
|
+
if (branch !== propertySchema.type) {
|
|
43
48
|
return {
|
|
44
49
|
code: "invalid_data_source_query_filter",
|
|
45
|
-
message: `Data source query filter branch "
|
|
50
|
+
message: `Data source query filter branch "${branch}" does not match property type "${propertySchema.type}".`,
|
|
46
51
|
isRetryable: false,
|
|
47
52
|
}
|
|
48
53
|
}
|
|
@@ -15,7 +15,7 @@ const supportedDataSourceSortPropertyTypes = new Set<string>(
|
|
|
15
15
|
*/
|
|
16
16
|
export function validateQueryDataSourceSorts(
|
|
17
17
|
sorts: ResolvedCustomBlockDataSourceSort[] | undefined,
|
|
18
|
-
collectionSchema: NotionCollectionSchema
|
|
18
|
+
collectionSchema: NotionCollectionSchema,
|
|
19
19
|
): CustomBlockQueryDataSourceErrorInfo | undefined {
|
|
20
20
|
if (sorts === undefined) {
|
|
21
21
|
return undefined
|
|
@@ -39,10 +39,7 @@ export function validateQueryDataSourceSorts(
|
|
|
39
39
|
}
|
|
40
40
|
seenPropertyIds.add(sort.propertyId)
|
|
41
41
|
|
|
42
|
-
if (
|
|
43
|
-
collectionSchema !== undefined &&
|
|
44
|
-
!Object.hasOwn(collectionSchema.propertiesById, sort.propertyId)
|
|
45
|
-
) {
|
|
42
|
+
if (!Object.hasOwn(collectionSchema.propertiesById, sort.propertyId)) {
|
|
46
43
|
return {
|
|
47
44
|
code: "invalid_data_source_query_sort",
|
|
48
45
|
message: `Data source query sort property ID "${sort.propertyId}" is not defined in the bound data source schema.`,
|
|
@@ -50,11 +47,8 @@ export function validateQueryDataSourceSorts(
|
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
const propertySchema = collectionSchema
|
|
54
|
-
if (
|
|
55
|
-
propertySchema !== undefined &&
|
|
56
|
-
!supportedDataSourceSortPropertyTypes.has(propertySchema.type)
|
|
57
|
-
) {
|
|
50
|
+
const propertySchema = collectionSchema.propertiesById[sort.propertyId]
|
|
51
|
+
if (!supportedDataSourceSortPropertyTypes.has(propertySchema.type)) {
|
|
58
52
|
return {
|
|
59
53
|
code: "invalid_data_source_query_sort",
|
|
60
54
|
message: `Data source query sorts do not yet support property type "${propertySchema.type}".`,
|