@notionhq/custom-blocks-host 0.0.1
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 +204 -0
- package/dist/autoBindProperties.d.ts +21 -0
- package/dist/autoBindProperties.d.ts.map +1 -0
- package/dist/autoBindProperties.js +38 -0
- package/dist/createCustomBlockHost.d.ts +46 -0
- package/dist/createCustomBlockHost.d.ts.map +1 -0
- package/dist/createCustomBlockHost.js +433 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lifecycle/initErrors.d.ts +13 -0
- package/dist/lifecycle/initErrors.d.ts.map +1 -0
- package/dist/lifecycle/initErrors.js +152 -0
- package/dist/lifecycle/initializationController.d.ts +54 -0
- package/dist/lifecycle/initializationController.d.ts.map +1 -0
- package/dist/lifecycle/initializationController.js +182 -0
- package/dist/lifecycle/protocolVersion.d.ts +2 -0
- package/dist/lifecycle/protocolVersion.d.ts.map +1 -0
- package/dist/lifecycle/protocolVersion.js +3 -0
- package/dist/lifecycle/types.d.ts +28 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lifecycle/types.js +1 -0
- package/dist/messages/invalidSandboxMessage.d.ts +7 -0
- package/dist/messages/invalidSandboxMessage.d.ts.map +1 -0
- package/dist/messages/invalidSandboxMessage.js +21 -0
- package/dist/messages/types.d.ts +29 -0
- package/dist/messages/types.d.ts.map +1 -0
- package/dist/messages/types.js +1 -0
- package/dist/protocol/contrast.d.ts +4 -0
- package/dist/protocol/contrast.js +3 -0
- package/dist/protocol/dataSources/dataSource.d.ts +731 -0
- package/dist/protocol/dataSources/dataSource.js +37 -0
- package/dist/protocol/dataSources/dataSourcePage.d.ts +72 -0
- package/dist/protocol/dataSources/dataSourcePage.js +12 -0
- package/dist/protocol/dataSources/dataSourceValue.d.ts +66 -0
- package/dist/protocol/dataSources/dataSourceValue.js +14 -0
- package/dist/protocol/dataSources/dateValue.d.ts +157 -0
- package/dist/protocol/dataSources/dateValue.js +59 -0
- package/dist/protocol/dataSources/errors.d.ts +8 -0
- package/dist/protocol/dataSources/errors.js +7 -0
- package/dist/protocol/dataSources/propertySchema.d.ts +197 -0
- package/dist/protocol/dataSources/propertySchema.js +165 -0
- package/dist/protocol/dataSources/recordPointer.d.ts +9 -0
- package/dist/protocol/dataSources/recordPointer.js +8 -0
- package/dist/protocol/errors.d.ts +12 -0
- package/dist/protocol/errors.js +6 -0
- package/dist/protocol/ids.d.ts +31 -0
- package/dist/protocol/ids.js +26 -0
- package/dist/protocol/incomingType.d.ts +9 -0
- package/dist/protocol/incomingType.js +17 -0
- package/dist/protocol/index.d.ts +48 -0
- package/dist/protocol/index.js +48 -0
- package/dist/protocol/manifest.d.ts +60 -0
- package/dist/protocol/manifest.js +32 -0
- package/dist/protocol/messagePayload.d.ts +19 -0
- package/dist/protocol/messagePayload.js +1 -0
- package/dist/protocol/messages/connect.d.ts +47 -0
- package/dist/protocol/messages/connect.js +38 -0
- package/dist/protocol/messages/contrastModeChanged.d.ts +7 -0
- package/dist/protocol/messages/contrastModeChanged.js +7 -0
- package/dist/protocol/messages/createPage.d.ts +200 -0
- package/dist/protocol/messages/createPage.js +52 -0
- package/dist/protocol/messages/createPageResult.d.ts +213 -0
- package/dist/protocol/messages/createPageResult.js +28 -0
- package/dist/protocol/messages/currentUserChanged.d.ts +18 -0
- package/dist/protocol/messages/currentUserChanged.js +9 -0
- package/dist/protocol/messages/dataSourcesChanged.d.ts +157 -0
- package/dist/protocol/messages/dataSourcesChanged.js +13 -0
- package/dist/protocol/messages/getPage.d.ts +218 -0
- package/dist/protocol/messages/getPage.js +33 -0
- package/dist/protocol/messages/getUser.d.ts +44 -0
- package/dist/protocol/messages/getUser.js +32 -0
- package/dist/protocol/messages/hostToSandbox.d.ts +1167 -0
- package/dist/protocol/messages/hostToSandbox.js +34 -0
- package/dist/protocol/messages/init.d.ts +259 -0
- package/dist/protocol/messages/init.js +56 -0
- package/dist/protocol/messages/initResult.d.ts +37 -0
- package/dist/protocol/messages/initResult.js +28 -0
- package/dist/protocol/messages/invalidHostMessage.d.ts +14 -0
- package/dist/protocol/messages/invalidHostMessage.js +13 -0
- package/dist/protocol/messages/invalidSandboxMessage.d.ts +14 -0
- package/dist/protocol/messages/invalidSandboxMessage.js +13 -0
- package/dist/protocol/messages/listUsers.d.ts +52 -0
- package/dist/protocol/messages/listUsers.js +33 -0
- package/dist/protocol/messages/pageChanged.d.ts +27 -0
- package/dist/protocol/messages/pageChanged.js +9 -0
- package/dist/protocol/messages/parentChanged.d.ts +24 -0
- package/dist/protocol/messages/parentChanged.js +9 -0
- package/dist/protocol/messages/queryDataSource.d.ts +985 -0
- package/dist/protocol/messages/queryDataSource.js +123 -0
- package/dist/protocol/messages/queryDataSourceResult.d.ts +93 -0
- package/dist/protocol/messages/queryDataSourceResult.js +28 -0
- package/dist/protocol/messages/resize.d.ts +11 -0
- package/dist/protocol/messages/resize.js +10 -0
- package/dist/protocol/messages/sandboxToHost.d.ts +744 -0
- package/dist/protocol/messages/sandboxToHost.js +27 -0
- package/dist/protocol/messages/themeChanged.d.ts +10 -0
- package/dist/protocol/messages/themeChanged.js +10 -0
- package/dist/protocol/messages/updatePage.d.ts +180 -0
- package/dist/protocol/messages/updatePage.js +22 -0
- package/dist/protocol/messages/updatePageResult.d.ts +212 -0
- package/dist/protocol/messages/updatePageResult.js +27 -0
- package/dist/protocol/pages/page.d.ts +597 -0
- package/dist/protocol/pages/page.js +212 -0
- package/dist/protocol/parent.d.ts +28 -0
- package/dist/protocol/parent.js +12 -0
- package/dist/protocol/protocolVersion.d.ts +6 -0
- package/dist/protocol/protocolVersion.js +6 -0
- package/dist/protocol/theme.d.ts +3 -0
- package/dist/protocol/theme.js +2 -0
- package/dist/protocol/users/user.d.ts +32 -0
- package/dist/protocol/users/user.js +20 -0
- package/dist/queries/querySubscriptions.d.ts +17 -0
- package/dist/queries/querySubscriptions.d.ts.map +1 -0
- package/dist/queries/querySubscriptions.js +38 -0
- package/dist/queries/types.d.ts +4 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +1 -0
- package/dist/queries/validateQueryDataSourceFilter.d.ts +5 -0
- package/dist/queries/validateQueryDataSourceFilter.d.ts.map +1 -0
- package/dist/queries/validateQueryDataSourceFilter.js +72 -0
- package/dist/queries/validateQueryDataSourceSorts.d.ts +8 -0
- package/dist/queries/validateQueryDataSourceSorts.d.ts.map +1 -0
- package/dist/queries/validateQueryDataSourceSorts.js +47 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +6 -0
- package/package.json +26 -0
- package/src/autoBindProperties.ts +63 -0
- package/src/createCustomBlockHost.ts +605 -0
- package/src/index.ts +25 -0
- package/src/lifecycle/initErrors.ts +165 -0
- package/src/lifecycle/initializationController.ts +290 -0
- package/src/lifecycle/protocolVersion.ts +3 -0
- package/src/lifecycle/types.ts +40 -0
- package/src/messages/invalidSandboxMessage.ts +35 -0
- package/src/messages/types.ts +80 -0
- package/src/queries/querySubscriptions.ts +57 -0
- package/src/queries/types.ts +10 -0
- package/src/queries/validateQueryDataSourceFilter.ts +101 -0
- package/src/queries/validateQueryDataSourceSorts.ts +69 -0
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion page shapes exchanged over the custom block bridge.
|
|
3
|
+
*
|
|
4
|
+
* These mirror a narrow subset of Notion's public `POST /v1/pages` API, since that's the bridge
|
|
5
|
+
* shape the host delivers for messages like `createPageResult`.
|
|
6
|
+
*/
|
|
7
|
+
import * as v from "valibot";
|
|
8
|
+
import { notionPageIdSchema } from "../ids.js";
|
|
9
|
+
import { notionParentSchema } from "../parent.js";
|
|
10
|
+
/**
|
|
11
|
+
* The custom block's nearest enclosing page ancestor, carried in `init.page` and `pageChanged`.
|
|
12
|
+
*/
|
|
13
|
+
export const customBlockPageSchema = v.object({
|
|
14
|
+
id: notionPageIdSchema,
|
|
15
|
+
/**
|
|
16
|
+
* The containing page's own parent. It lets a block distinguish a freestanding page from a
|
|
17
|
+
* database row without a round trip. Not to be confused with the top-level `parent` runtime
|
|
18
|
+
* value, which is the custom block's own parent.
|
|
19
|
+
*/
|
|
20
|
+
parent: notionParentSchema,
|
|
21
|
+
});
|
|
22
|
+
export const notionEmojiIconSchema = v.object({
|
|
23
|
+
type: v.literal("emoji"),
|
|
24
|
+
emoji: v.string(),
|
|
25
|
+
});
|
|
26
|
+
export const notionCustomEmojiIconSchema = v.object({
|
|
27
|
+
type: v.literal("custom_emoji"),
|
|
28
|
+
custom_emoji: v.object({
|
|
29
|
+
id: v.string(),
|
|
30
|
+
name: v.optional(v.string()),
|
|
31
|
+
url: v.optional(v.string()),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
export const notionExternalFileSchema = v.object({
|
|
35
|
+
type: v.literal("external"),
|
|
36
|
+
external: v.object({ url: v.string() }),
|
|
37
|
+
});
|
|
38
|
+
export const notionHostedFileSchema = v.object({
|
|
39
|
+
type: v.literal("file"),
|
|
40
|
+
file: v.object({
|
|
41
|
+
url: v.string(),
|
|
42
|
+
expiry_time: v.optional(v.string()),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
// TODO(custom-blocks): Add file upload support.
|
|
46
|
+
export const notionPageIconSchema = v.variant("type", [
|
|
47
|
+
notionEmojiIconSchema,
|
|
48
|
+
notionCustomEmojiIconSchema,
|
|
49
|
+
notionExternalFileSchema,
|
|
50
|
+
notionHostedFileSchema,
|
|
51
|
+
]);
|
|
52
|
+
export const notionPageCoverSchema = v.variant("type", [
|
|
53
|
+
notionExternalFileSchema,
|
|
54
|
+
notionHostedFileSchema,
|
|
55
|
+
]);
|
|
56
|
+
const nullableStringSchema = v.nullable(v.string());
|
|
57
|
+
const notionRichTextItemSchema = v.record(v.string(), v.unknown());
|
|
58
|
+
const notionSelectOptionInputSchema = v.union([
|
|
59
|
+
v.object({
|
|
60
|
+
id: v.string(),
|
|
61
|
+
name: v.optional(v.string()),
|
|
62
|
+
color: v.optional(v.string()),
|
|
63
|
+
description: v.optional(nullableStringSchema),
|
|
64
|
+
}),
|
|
65
|
+
v.object({
|
|
66
|
+
name: v.string(),
|
|
67
|
+
id: v.optional(v.string()),
|
|
68
|
+
color: v.optional(v.string()),
|
|
69
|
+
description: v.optional(nullableStringSchema),
|
|
70
|
+
}),
|
|
71
|
+
]);
|
|
72
|
+
const notionDateInputSchema = v.object({
|
|
73
|
+
start: v.string(),
|
|
74
|
+
end: v.optional(nullableStringSchema),
|
|
75
|
+
time_zone: v.optional(nullableStringSchema),
|
|
76
|
+
});
|
|
77
|
+
const notionUserInputSchema = v.union([
|
|
78
|
+
v.object({
|
|
79
|
+
object: v.optional(v.literal("user")),
|
|
80
|
+
id: v.string(),
|
|
81
|
+
}),
|
|
82
|
+
v.object({
|
|
83
|
+
object: v.literal("group"),
|
|
84
|
+
id: v.string(),
|
|
85
|
+
name: v.optional(nullableStringSchema),
|
|
86
|
+
}),
|
|
87
|
+
]);
|
|
88
|
+
const notionRelationInputSchema = v.object({ id: v.string() });
|
|
89
|
+
const notionFileInputSchema = v.union([
|
|
90
|
+
v.object({
|
|
91
|
+
type: v.literal("external"),
|
|
92
|
+
name: v.string(),
|
|
93
|
+
external: v.object({ url: v.string() }),
|
|
94
|
+
}),
|
|
95
|
+
v.object({
|
|
96
|
+
type: v.literal("file"),
|
|
97
|
+
name: v.string(),
|
|
98
|
+
file: v.object({
|
|
99
|
+
url: v.string(),
|
|
100
|
+
expiry_time: v.optional(v.string()),
|
|
101
|
+
}),
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
const notionPlaceInputSchema = v.object({
|
|
105
|
+
lat: v.number(),
|
|
106
|
+
lon: v.number(),
|
|
107
|
+
name: v.optional(nullableStringSchema),
|
|
108
|
+
address: v.optional(nullableStringSchema),
|
|
109
|
+
aws_place_id: v.optional(nullableStringSchema),
|
|
110
|
+
google_place_id: v.optional(nullableStringSchema),
|
|
111
|
+
});
|
|
112
|
+
const pagePropertyBaseSchema = {
|
|
113
|
+
id: v.string(),
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* A page property value.
|
|
117
|
+
*/
|
|
118
|
+
export const notionPagePropertyValueSchema = v.variant("type", [
|
|
119
|
+
v.object({
|
|
120
|
+
...pagePropertyBaseSchema,
|
|
121
|
+
type: v.literal("title"),
|
|
122
|
+
title: v.array(notionRichTextItemSchema),
|
|
123
|
+
}),
|
|
124
|
+
v.object({
|
|
125
|
+
...pagePropertyBaseSchema,
|
|
126
|
+
type: v.literal("rich_text"),
|
|
127
|
+
rich_text: v.array(notionRichTextItemSchema),
|
|
128
|
+
}),
|
|
129
|
+
v.object({
|
|
130
|
+
...pagePropertyBaseSchema,
|
|
131
|
+
type: v.literal("number"),
|
|
132
|
+
number: v.nullable(v.number()),
|
|
133
|
+
}),
|
|
134
|
+
v.object({
|
|
135
|
+
...pagePropertyBaseSchema,
|
|
136
|
+
type: v.literal("url"),
|
|
137
|
+
url: nullableStringSchema,
|
|
138
|
+
}),
|
|
139
|
+
v.object({
|
|
140
|
+
...pagePropertyBaseSchema,
|
|
141
|
+
type: v.literal("email"),
|
|
142
|
+
email: nullableStringSchema,
|
|
143
|
+
}),
|
|
144
|
+
v.object({
|
|
145
|
+
...pagePropertyBaseSchema,
|
|
146
|
+
type: v.literal("phone_number"),
|
|
147
|
+
phone_number: nullableStringSchema,
|
|
148
|
+
}),
|
|
149
|
+
v.object({
|
|
150
|
+
...pagePropertyBaseSchema,
|
|
151
|
+
type: v.literal("checkbox"),
|
|
152
|
+
checkbox: v.boolean(),
|
|
153
|
+
}),
|
|
154
|
+
v.object({
|
|
155
|
+
...pagePropertyBaseSchema,
|
|
156
|
+
type: v.literal("select"),
|
|
157
|
+
select: v.nullable(notionSelectOptionInputSchema),
|
|
158
|
+
}),
|
|
159
|
+
v.object({
|
|
160
|
+
...pagePropertyBaseSchema,
|
|
161
|
+
type: v.literal("status"),
|
|
162
|
+
status: v.nullable(notionSelectOptionInputSchema),
|
|
163
|
+
}),
|
|
164
|
+
v.object({
|
|
165
|
+
...pagePropertyBaseSchema,
|
|
166
|
+
type: v.literal("multi_select"),
|
|
167
|
+
multi_select: v.array(notionSelectOptionInputSchema),
|
|
168
|
+
}),
|
|
169
|
+
v.object({
|
|
170
|
+
...pagePropertyBaseSchema,
|
|
171
|
+
type: v.literal("date"),
|
|
172
|
+
date: v.nullable(notionDateInputSchema),
|
|
173
|
+
}),
|
|
174
|
+
v.object({
|
|
175
|
+
...pagePropertyBaseSchema,
|
|
176
|
+
type: v.literal("people"),
|
|
177
|
+
people: v.array(notionUserInputSchema),
|
|
178
|
+
}),
|
|
179
|
+
v.object({
|
|
180
|
+
...pagePropertyBaseSchema,
|
|
181
|
+
type: v.literal("relation"),
|
|
182
|
+
has_more: v.optional(v.boolean()),
|
|
183
|
+
relation: v.array(notionRelationInputSchema),
|
|
184
|
+
}),
|
|
185
|
+
v.object({
|
|
186
|
+
...pagePropertyBaseSchema,
|
|
187
|
+
type: v.literal("files"),
|
|
188
|
+
files: v.array(notionFileInputSchema),
|
|
189
|
+
}),
|
|
190
|
+
v.object({
|
|
191
|
+
...pagePropertyBaseSchema,
|
|
192
|
+
type: v.literal("place"),
|
|
193
|
+
place: v.nullable(notionPlaceInputSchema),
|
|
194
|
+
}),
|
|
195
|
+
]);
|
|
196
|
+
export const notionPagePropertyWriteMapSchema = v.record(v.string(), notionPagePropertyValueSchema);
|
|
197
|
+
/**
|
|
198
|
+
* The `Page` object returned in `createPageResult` messages.
|
|
199
|
+
*/
|
|
200
|
+
export const notionPageSchema = v.object({
|
|
201
|
+
object: v.literal("page"),
|
|
202
|
+
id: notionPageIdSchema,
|
|
203
|
+
parent: notionParentSchema,
|
|
204
|
+
properties: notionPagePropertyWriteMapSchema,
|
|
205
|
+
created_time: v.optional(v.string()),
|
|
206
|
+
last_edited_time: v.optional(v.string()),
|
|
207
|
+
icon: v.optional(notionPageIconSchema),
|
|
208
|
+
cover: v.optional(notionPageCoverSchema),
|
|
209
|
+
url: v.optional(v.string()),
|
|
210
|
+
public_url: v.optional(v.string()),
|
|
211
|
+
in_trash: v.optional(v.boolean()),
|
|
212
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export declare const notionParentSchema: v.VariantSchema<"type", [v.ObjectSchema<{
|
|
3
|
+
readonly type: v.LiteralSchema<"page_id", undefined>;
|
|
4
|
+
readonly page_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionPageId">]>;
|
|
5
|
+
}, undefined>, v.ObjectSchema<{
|
|
6
|
+
readonly type: v.LiteralSchema<"data_source_id", undefined>;
|
|
7
|
+
readonly data_source_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionDataSourceId">]>;
|
|
8
|
+
}, undefined>, v.ObjectSchema<{
|
|
9
|
+
readonly type: v.LiteralSchema<"workspace", undefined>;
|
|
10
|
+
readonly workspace: v.LiteralSchema<true, undefined>;
|
|
11
|
+
}, undefined>, v.ObjectSchema<{
|
|
12
|
+
readonly type: v.LiteralSchema<"block_id", undefined>;
|
|
13
|
+
readonly block_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionBlockId">]>;
|
|
14
|
+
}, undefined>, v.ObjectSchema<{
|
|
15
|
+
readonly type: v.LiteralSchema<"agent_id", undefined>;
|
|
16
|
+
readonly agent_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionAgentId">]>;
|
|
17
|
+
}, undefined>], undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* A record's parent reference.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors Notion's public API:
|
|
22
|
+
* - `page_id` — parented directly under a page.
|
|
23
|
+
* - `block_id` — parented under a non-page container block (toggle, column, callout, ...).
|
|
24
|
+
* - `data_source_id` — parented under a collection (data source).
|
|
25
|
+
* - `workspace` — top-level / team-rooted.
|
|
26
|
+
* - `agent_id` — owned by an agent, such as an agent instruction page or its blocks.
|
|
27
|
+
*/
|
|
28
|
+
export type NotionParent = v.InferOutput<typeof notionParentSchema>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { notionAgentIdSchema, notionBlockIdSchema, notionDataSourceIdSchema, notionPageIdSchema, } from "./ids.js";
|
|
3
|
+
export const notionParentSchema = v.variant("type", [
|
|
4
|
+
v.object({ type: v.literal("page_id"), page_id: notionPageIdSchema }),
|
|
5
|
+
v.object({
|
|
6
|
+
type: v.literal("data_source_id"),
|
|
7
|
+
data_source_id: notionDataSourceIdSchema,
|
|
8
|
+
}),
|
|
9
|
+
v.object({ type: v.literal("workspace"), workspace: v.literal(true) }),
|
|
10
|
+
v.object({ type: v.literal("block_id"), block_id: notionBlockIdSchema }),
|
|
11
|
+
v.object({ type: v.literal("agent_id"), agent_id: notionAgentIdSchema }),
|
|
12
|
+
]);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used to ensure that the host and client are using the same version of the bridge protocol. A
|
|
3
|
+
* single host needs to support multiple custom blocks built with different versions of the bridge
|
|
4
|
+
* protocol. Increment this number any time a breaking change is made to the bridge protocol.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 3;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used to ensure that the host and client are using the same version of the bridge protocol. A
|
|
3
|
+
* single host needs to support multiple custom blocks built with different versions of the bridge
|
|
4
|
+
* protocol. Increment this number any time a breaking change is made to the bridge protocol.
|
|
5
|
+
*/
|
|
6
|
+
export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 3;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export declare const notionUserIdSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionUserId">]>;
|
|
3
|
+
export type NotionUserId = v.InferOutput<typeof notionUserIdSchema>;
|
|
4
|
+
export declare const notionUserSchema: v.ObjectSchema<{
|
|
5
|
+
readonly object: v.LiteralSchema<"user", undefined>;
|
|
6
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionUserId">]>;
|
|
7
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly avatar_url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
9
|
+
readonly type: v.LiteralSchema<"person", undefined>;
|
|
10
|
+
readonly person: v.ObjectSchema<{
|
|
11
|
+
readonly email: v.StringSchema<undefined>;
|
|
12
|
+
}, undefined>;
|
|
13
|
+
}, undefined>;
|
|
14
|
+
export type NotionUser = v.InferOutput<typeof notionUserSchema>;
|
|
15
|
+
export declare const notionUserListSchema: v.ObjectSchema<{
|
|
16
|
+
readonly object: v.LiteralSchema<"list", undefined>;
|
|
17
|
+
readonly results: v.ArraySchema<v.ObjectSchema<{
|
|
18
|
+
readonly object: v.LiteralSchema<"user", undefined>;
|
|
19
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.BrandAction<string, "NotionUserId">]>;
|
|
20
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
21
|
+
readonly avatar_url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
22
|
+
readonly type: v.LiteralSchema<"person", undefined>;
|
|
23
|
+
readonly person: v.ObjectSchema<{
|
|
24
|
+
readonly email: v.StringSchema<undefined>;
|
|
25
|
+
}, undefined>;
|
|
26
|
+
}, undefined>, undefined>;
|
|
27
|
+
readonly next_cursor: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
28
|
+
readonly has_more: v.BooleanSchema<undefined>;
|
|
29
|
+
readonly type: v.LiteralSchema<"user", undefined>;
|
|
30
|
+
readonly user: v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>;
|
|
31
|
+
}, undefined>;
|
|
32
|
+
export type NotionUserList = v.InferOutput<typeof notionUserListSchema>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export const notionUserIdSchema = v.pipe(v.string(), v.brand("NotionUserId"));
|
|
3
|
+
export const notionUserSchema = v.object({
|
|
4
|
+
object: v.literal("user"),
|
|
5
|
+
id: notionUserIdSchema,
|
|
6
|
+
name: v.optional(v.string()),
|
|
7
|
+
avatar_url: v.nullable(v.string()),
|
|
8
|
+
type: v.literal("person"),
|
|
9
|
+
person: v.object({
|
|
10
|
+
email: v.string(),
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
export const notionUserListSchema = v.object({
|
|
14
|
+
object: v.literal("list"),
|
|
15
|
+
results: v.array(notionUserSchema),
|
|
16
|
+
next_cursor: v.nullable(v.string()),
|
|
17
|
+
has_more: v.boolean(),
|
|
18
|
+
type: v.literal("user"),
|
|
19
|
+
user: v.record(v.string(), v.unknown()),
|
|
20
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NotionDataSourceId } from "../protocol/ids.js";
|
|
2
|
+
import type { QueryDataSourceMessage } from "../protocol/messages/queryDataSource.js";
|
|
3
|
+
export type QueryRefresh = {
|
|
4
|
+
message: QueryDataSourceMessage;
|
|
5
|
+
token: QuerySubscriptionToken;
|
|
6
|
+
};
|
|
7
|
+
export type QuerySubscriptionToken = {
|
|
8
|
+
readonly _tag: "QuerySubscriptionToken";
|
|
9
|
+
};
|
|
10
|
+
export declare class QuerySubscriptions {
|
|
11
|
+
private readonly bySubscriptionId;
|
|
12
|
+
track(message: QueryDataSourceMessage): QuerySubscriptionToken;
|
|
13
|
+
isCurrent(subscriptionId: string, token: QuerySubscriptionToken): boolean;
|
|
14
|
+
refresh(dataSourceId: NotionDataSourceId): QueryRefresh[];
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=querySubscriptions.d.ts.map
|
|
@@ -0,0 +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,KAAK,CAAC,OAAO,EAAE,sBAAsB,GAAG,sBAAsB;IAW9D,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO;IAIzE,OAAO,CAAC,YAAY,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAkBzD,KAAK;CAGL"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class QuerySubscriptions {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.bySubscriptionId = new Map();
|
|
4
|
+
}
|
|
5
|
+
track(message) {
|
|
6
|
+
const token = {
|
|
7
|
+
_tag: "QuerySubscriptionToken",
|
|
8
|
+
};
|
|
9
|
+
this.bySubscriptionId.set(message.subscriptionId, {
|
|
10
|
+
message,
|
|
11
|
+
token,
|
|
12
|
+
});
|
|
13
|
+
return token;
|
|
14
|
+
}
|
|
15
|
+
isCurrent(subscriptionId, token) {
|
|
16
|
+
return this.bySubscriptionId.get(subscriptionId)?.token === token;
|
|
17
|
+
}
|
|
18
|
+
refresh(dataSourceId) {
|
|
19
|
+
const refreshes = [];
|
|
20
|
+
for (const queryState of this.bySubscriptionId.values()) {
|
|
21
|
+
if (queryState.message.dataSourceId !== dataSourceId) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const token = {
|
|
25
|
+
_tag: "QuerySubscriptionToken",
|
|
26
|
+
};
|
|
27
|
+
queryState.token = token;
|
|
28
|
+
refreshes.push({
|
|
29
|
+
message: queryState.message,
|
|
30
|
+
token,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return refreshes;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
this.bySubscriptionId.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BridgeMessagePayload } from "../protocol/messagePayload.js";
|
|
2
|
+
import type { CustomBlockQueryDataSourceErrorInfo, QueryDataSourceResultMessage } from "../protocol/messages/queryDataSourceResult.js";
|
|
3
|
+
export type CustomBlockHostQueryDataSourceResult = BridgeMessagePayload<QueryDataSourceResultMessage, CustomBlockQueryDataSourceErrorInfo>;
|
|
4
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/queries/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAA;AAC9F,OAAO,KAAK,EACX,mCAAmC,EACnC,4BAA4B,EAC5B,MAAM,oEAAoE,CAAA;AAE3E,MAAM,MAAM,oCAAoC,GAAG,oBAAoB,CACtE,4BAA4B,EAC5B,mCAAmC,CACnC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NotionCollectionSchema } from "../protocol/dataSources/dataSource.js";
|
|
2
|
+
import type { ResolvedCustomBlockDataSourceFilter } from "../protocol/messages/queryDataSource.js";
|
|
3
|
+
import type { CustomBlockQueryDataSourceErrorInfo } from "../protocol/messages/queryDataSourceResult.js";
|
|
4
|
+
export declare function validateQueryDataSourceFilter(filter: ResolvedCustomBlockDataSourceFilter | undefined, collectionSchema: NotionCollectionSchema | undefined): CustomBlockQueryDataSourceErrorInfo | undefined;
|
|
5
|
+
//# sourceMappingURL=validateQueryDataSourceFilter.d.ts.map
|
|
@@ -0,0 +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,GAAG,SAAS,GAClD,mCAAmC,GAAG,SAAS,CAajD"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { unreachable } from "../utils.js";
|
|
2
|
+
export function validateQueryDataSourceFilter(filter, collectionSchema) {
|
|
3
|
+
if (filter === undefined || collectionSchema === undefined) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const propertyFilters = "and" in filter ? filter.and : [filter];
|
|
7
|
+
for (const propertyFilter of propertyFilters) {
|
|
8
|
+
const error = validatePropertyFilter(propertyFilter, collectionSchema);
|
|
9
|
+
if (error !== undefined) {
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
function validatePropertyFilter(filter, collectionSchema) {
|
|
16
|
+
const propertySchema = collectionSchema.propertiesById[filter.propertyId];
|
|
17
|
+
if (propertySchema === undefined) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const optionFilter = getOptionFilter(filter);
|
|
21
|
+
if (optionFilter === undefined ||
|
|
22
|
+
propertySchema.type !== optionFilter.propertyType) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const optionNames = new Set(propertySchema.options.map(option => option.name));
|
|
26
|
+
const invalidOptionName = getOptionFilterValues(optionFilter.operator).find(optionName => !optionNames.has(optionName));
|
|
27
|
+
if (invalidOptionName === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
code: "invalid_data_source_query_filter",
|
|
32
|
+
message: `Data source query filter option "${invalidOptionName}" is not defined for property "${filter.propertyId}".`,
|
|
33
|
+
isRetryable: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function getOptionFilter(filter) {
|
|
37
|
+
if ("select" in filter) {
|
|
38
|
+
return { propertyType: "select", operator: filter.select };
|
|
39
|
+
}
|
|
40
|
+
if ("multi_select" in filter) {
|
|
41
|
+
return { propertyType: "multi_select", operator: filter.multi_select };
|
|
42
|
+
}
|
|
43
|
+
if ("status" in filter) {
|
|
44
|
+
return { propertyType: "status", operator: filter.status };
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
function getOptionFilterValues(operator) {
|
|
49
|
+
let value;
|
|
50
|
+
if ("equals" in operator) {
|
|
51
|
+
value = operator.equals;
|
|
52
|
+
}
|
|
53
|
+
else if ("does_not_equal" in operator) {
|
|
54
|
+
value = operator.does_not_equal;
|
|
55
|
+
}
|
|
56
|
+
else if ("contains" in operator) {
|
|
57
|
+
value = operator.contains;
|
|
58
|
+
}
|
|
59
|
+
else if ("contains_all" in operator) {
|
|
60
|
+
value = operator.contains_all;
|
|
61
|
+
}
|
|
62
|
+
else if ("does_not_contain" in operator) {
|
|
63
|
+
value = operator.does_not_contain;
|
|
64
|
+
}
|
|
65
|
+
else if ("is_empty" in operator || "is_not_empty" in operator) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return unreachable(operator);
|
|
70
|
+
}
|
|
71
|
+
return Array.isArray(value) ? value : [value];
|
|
72
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NotionCollectionSchema } from "../protocol/dataSources/dataSource.js";
|
|
2
|
+
import { type ResolvedCustomBlockDataSourceSort } from "../protocol/messages/queryDataSource.js";
|
|
3
|
+
import type { CustomBlockQueryDataSourceErrorInfo } from "../protocol/messages/queryDataSourceResult.js";
|
|
4
|
+
/**
|
|
5
|
+
* Ensures the sorts of a data source query are valid.
|
|
6
|
+
*/
|
|
7
|
+
export declare function validateQueryDataSourceSorts(sorts: ResolvedCustomBlockDataSourceSort[] | undefined, collectionSchema: NotionCollectionSchema | undefined): CustomBlockQueryDataSourceErrorInfo | undefined;
|
|
8
|
+
//# sourceMappingURL=validateQueryDataSourceSorts.d.ts.map
|
|
@@ -0,0 +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,GAAG,SAAS,GAClD,mCAAmC,GAAG,SAAS,CAkDjD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CUSTOM_BLOCK_DATA_SOURCE_SORTABLE_PROPERTY_TYPES } from "../protocol/dataSources/propertySchema.js";
|
|
2
|
+
import { CUSTOM_BLOCK_DATA_SOURCE_QUERY_MAX_SORTS, } from "../protocol/messages/queryDataSource.js";
|
|
3
|
+
const supportedDataSourceSortPropertyTypes = new Set(CUSTOM_BLOCK_DATA_SOURCE_SORTABLE_PROPERTY_TYPES);
|
|
4
|
+
/**
|
|
5
|
+
* Ensures the sorts of a data source query are valid.
|
|
6
|
+
*/
|
|
7
|
+
export function validateQueryDataSourceSorts(sorts, collectionSchema) {
|
|
8
|
+
if (sorts === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
if (sorts.length > CUSTOM_BLOCK_DATA_SOURCE_QUERY_MAX_SORTS) {
|
|
12
|
+
return {
|
|
13
|
+
code: "invalid_data_source_query_sort",
|
|
14
|
+
message: `Data source query sorts may contain at most ${CUSTOM_BLOCK_DATA_SOURCE_QUERY_MAX_SORTS} entries.`,
|
|
15
|
+
isRetryable: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const seenPropertyIds = new Set();
|
|
19
|
+
for (const sort of sorts) {
|
|
20
|
+
if (seenPropertyIds.has(sort.propertyId)) {
|
|
21
|
+
return {
|
|
22
|
+
code: "invalid_data_source_query_sort",
|
|
23
|
+
message: `Data source query sorts cannot contain duplicate property ID "${sort.propertyId}".`,
|
|
24
|
+
isRetryable: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
seenPropertyIds.add(sort.propertyId);
|
|
28
|
+
if (collectionSchema !== undefined &&
|
|
29
|
+
!Object.prototype.hasOwnProperty.call(collectionSchema.propertiesById, sort.propertyId)) {
|
|
30
|
+
return {
|
|
31
|
+
code: "invalid_data_source_query_sort",
|
|
32
|
+
message: `Data source query sort property ID "${sort.propertyId}" is not defined in the bound data source schema.`,
|
|
33
|
+
isRetryable: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const propertySchema = collectionSchema?.propertiesById[sort.propertyId];
|
|
37
|
+
if (propertySchema !== undefined &&
|
|
38
|
+
!supportedDataSourceSortPropertyTypes.has(propertySchema.type)) {
|
|
39
|
+
return {
|
|
40
|
+
code: "invalid_data_source_query_sort",
|
|
41
|
+
message: `Data source query sorts do not yet support property type "${propertySchema.type}".`,
|
|
42
|
+
isRetryable: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAI/C"}
|
package/dist/utils.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notionhq/custom-blocks-host",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Reusable host runtime for Notion custom blocks.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"valibot": "^1.3.1"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { NotionPropertyType } from "@notionhq/custom-blocks-protocol/dataSources/propertySchema.js"
|
|
2
|
+
|
|
3
|
+
export type AutoBindableProperty = {
|
|
4
|
+
name: string
|
|
5
|
+
type: NotionPropertyType
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Finds property IDs for a manifest's properties without changing the
|
|
10
|
+
* manifest's semantic keys.
|
|
11
|
+
*
|
|
12
|
+
* For each manifest property:
|
|
13
|
+
* 1. Consider only schema properties of the same type.
|
|
14
|
+
* 2. A schema property is a candidate if its ID equals the manifest key
|
|
15
|
+
* (exact string: no trim, no case folding) or its display name equals
|
|
16
|
+
* the manifest display name after `trim()` and lowercasing.
|
|
17
|
+
* 3. Bind when there is exactly one candidate. Otherwise leave it unmapped.
|
|
18
|
+
*/
|
|
19
|
+
export function getAutoBoundPropertyIdsByKey(args: {
|
|
20
|
+
manifestProperties: Readonly<Record<string, AutoBindableProperty>>
|
|
21
|
+
propertySchemasById: Readonly<Record<string, AutoBindableProperty>>
|
|
22
|
+
}): Record<string, string> {
|
|
23
|
+
const { manifestProperties, propertySchemasById } = args
|
|
24
|
+
const mappings: Array<[string, string]> = []
|
|
25
|
+
|
|
26
|
+
for (const [propertyKey, manifestProperty] of Object.entries(
|
|
27
|
+
manifestProperties,
|
|
28
|
+
)) {
|
|
29
|
+
const propertyId = getAutoBoundPropertyId({
|
|
30
|
+
propertyKey,
|
|
31
|
+
manifestProperty,
|
|
32
|
+
propertySchemasById,
|
|
33
|
+
})
|
|
34
|
+
if (propertyId !== undefined) {
|
|
35
|
+
mappings.push([propertyKey, propertyId])
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Object.fromEntries(mappings)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getAutoBoundPropertyId(args: {
|
|
43
|
+
propertyKey: string
|
|
44
|
+
manifestProperty: AutoBindableProperty
|
|
45
|
+
propertySchemasById: Readonly<Record<string, AutoBindableProperty>>
|
|
46
|
+
}): string | undefined {
|
|
47
|
+
const { propertyKey, manifestProperty, propertySchemasById } = args
|
|
48
|
+
const normalizedManifestName = normalizePropertyDisplayName(
|
|
49
|
+
manifestProperty.name,
|
|
50
|
+
)
|
|
51
|
+
const candidates = Object.entries(propertySchemasById).filter(
|
|
52
|
+
([schemaId, propertySchema]) =>
|
|
53
|
+
propertySchema.type === manifestProperty.type &&
|
|
54
|
+
(schemaId === propertyKey ||
|
|
55
|
+
normalizePropertyDisplayName(propertySchema.name) ===
|
|
56
|
+
normalizedManifestName),
|
|
57
|
+
)
|
|
58
|
+
return candidates.length === 1 ? candidates[0][0] : undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizePropertyDisplayName(value: string): string {
|
|
62
|
+
return value.trim().toLowerCase()
|
|
63
|
+
}
|