@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.
Files changed (142) hide show
  1. package/README.md +204 -0
  2. package/dist/autoBindProperties.d.ts +21 -0
  3. package/dist/autoBindProperties.d.ts.map +1 -0
  4. package/dist/autoBindProperties.js +38 -0
  5. package/dist/createCustomBlockHost.d.ts +46 -0
  6. package/dist/createCustomBlockHost.d.ts.map +1 -0
  7. package/dist/createCustomBlockHost.js +433 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2 -0
  11. package/dist/lifecycle/initErrors.d.ts +13 -0
  12. package/dist/lifecycle/initErrors.d.ts.map +1 -0
  13. package/dist/lifecycle/initErrors.js +152 -0
  14. package/dist/lifecycle/initializationController.d.ts +54 -0
  15. package/dist/lifecycle/initializationController.d.ts.map +1 -0
  16. package/dist/lifecycle/initializationController.js +182 -0
  17. package/dist/lifecycle/protocolVersion.d.ts +2 -0
  18. package/dist/lifecycle/protocolVersion.d.ts.map +1 -0
  19. package/dist/lifecycle/protocolVersion.js +3 -0
  20. package/dist/lifecycle/types.d.ts +28 -0
  21. package/dist/lifecycle/types.d.ts.map +1 -0
  22. package/dist/lifecycle/types.js +1 -0
  23. package/dist/messages/invalidSandboxMessage.d.ts +7 -0
  24. package/dist/messages/invalidSandboxMessage.d.ts.map +1 -0
  25. package/dist/messages/invalidSandboxMessage.js +21 -0
  26. package/dist/messages/types.d.ts +29 -0
  27. package/dist/messages/types.d.ts.map +1 -0
  28. package/dist/messages/types.js +1 -0
  29. package/dist/protocol/contrast.d.ts +4 -0
  30. package/dist/protocol/contrast.js +3 -0
  31. package/dist/protocol/dataSources/dataSource.d.ts +731 -0
  32. package/dist/protocol/dataSources/dataSource.js +37 -0
  33. package/dist/protocol/dataSources/dataSourcePage.d.ts +72 -0
  34. package/dist/protocol/dataSources/dataSourcePage.js +12 -0
  35. package/dist/protocol/dataSources/dataSourceValue.d.ts +66 -0
  36. package/dist/protocol/dataSources/dataSourceValue.js +14 -0
  37. package/dist/protocol/dataSources/dateValue.d.ts +157 -0
  38. package/dist/protocol/dataSources/dateValue.js +59 -0
  39. package/dist/protocol/dataSources/errors.d.ts +8 -0
  40. package/dist/protocol/dataSources/errors.js +7 -0
  41. package/dist/protocol/dataSources/propertySchema.d.ts +197 -0
  42. package/dist/protocol/dataSources/propertySchema.js +165 -0
  43. package/dist/protocol/dataSources/recordPointer.d.ts +9 -0
  44. package/dist/protocol/dataSources/recordPointer.js +8 -0
  45. package/dist/protocol/errors.d.ts +12 -0
  46. package/dist/protocol/errors.js +6 -0
  47. package/dist/protocol/ids.d.ts +31 -0
  48. package/dist/protocol/ids.js +26 -0
  49. package/dist/protocol/incomingType.d.ts +9 -0
  50. package/dist/protocol/incomingType.js +17 -0
  51. package/dist/protocol/index.d.ts +48 -0
  52. package/dist/protocol/index.js +48 -0
  53. package/dist/protocol/manifest.d.ts +60 -0
  54. package/dist/protocol/manifest.js +32 -0
  55. package/dist/protocol/messagePayload.d.ts +19 -0
  56. package/dist/protocol/messagePayload.js +1 -0
  57. package/dist/protocol/messages/connect.d.ts +47 -0
  58. package/dist/protocol/messages/connect.js +38 -0
  59. package/dist/protocol/messages/contrastModeChanged.d.ts +7 -0
  60. package/dist/protocol/messages/contrastModeChanged.js +7 -0
  61. package/dist/protocol/messages/createPage.d.ts +200 -0
  62. package/dist/protocol/messages/createPage.js +52 -0
  63. package/dist/protocol/messages/createPageResult.d.ts +213 -0
  64. package/dist/protocol/messages/createPageResult.js +28 -0
  65. package/dist/protocol/messages/currentUserChanged.d.ts +18 -0
  66. package/dist/protocol/messages/currentUserChanged.js +9 -0
  67. package/dist/protocol/messages/dataSourcesChanged.d.ts +157 -0
  68. package/dist/protocol/messages/dataSourcesChanged.js +13 -0
  69. package/dist/protocol/messages/getPage.d.ts +218 -0
  70. package/dist/protocol/messages/getPage.js +33 -0
  71. package/dist/protocol/messages/getUser.d.ts +44 -0
  72. package/dist/protocol/messages/getUser.js +32 -0
  73. package/dist/protocol/messages/hostToSandbox.d.ts +1167 -0
  74. package/dist/protocol/messages/hostToSandbox.js +34 -0
  75. package/dist/protocol/messages/init.d.ts +259 -0
  76. package/dist/protocol/messages/init.js +56 -0
  77. package/dist/protocol/messages/initResult.d.ts +37 -0
  78. package/dist/protocol/messages/initResult.js +28 -0
  79. package/dist/protocol/messages/invalidHostMessage.d.ts +14 -0
  80. package/dist/protocol/messages/invalidHostMessage.js +13 -0
  81. package/dist/protocol/messages/invalidSandboxMessage.d.ts +14 -0
  82. package/dist/protocol/messages/invalidSandboxMessage.js +13 -0
  83. package/dist/protocol/messages/listUsers.d.ts +52 -0
  84. package/dist/protocol/messages/listUsers.js +33 -0
  85. package/dist/protocol/messages/pageChanged.d.ts +27 -0
  86. package/dist/protocol/messages/pageChanged.js +9 -0
  87. package/dist/protocol/messages/parentChanged.d.ts +24 -0
  88. package/dist/protocol/messages/parentChanged.js +9 -0
  89. package/dist/protocol/messages/queryDataSource.d.ts +985 -0
  90. package/dist/protocol/messages/queryDataSource.js +123 -0
  91. package/dist/protocol/messages/queryDataSourceResult.d.ts +93 -0
  92. package/dist/protocol/messages/queryDataSourceResult.js +28 -0
  93. package/dist/protocol/messages/resize.d.ts +11 -0
  94. package/dist/protocol/messages/resize.js +10 -0
  95. package/dist/protocol/messages/sandboxToHost.d.ts +744 -0
  96. package/dist/protocol/messages/sandboxToHost.js +27 -0
  97. package/dist/protocol/messages/themeChanged.d.ts +10 -0
  98. package/dist/protocol/messages/themeChanged.js +10 -0
  99. package/dist/protocol/messages/updatePage.d.ts +180 -0
  100. package/dist/protocol/messages/updatePage.js +22 -0
  101. package/dist/protocol/messages/updatePageResult.d.ts +212 -0
  102. package/dist/protocol/messages/updatePageResult.js +27 -0
  103. package/dist/protocol/pages/page.d.ts +597 -0
  104. package/dist/protocol/pages/page.js +212 -0
  105. package/dist/protocol/parent.d.ts +28 -0
  106. package/dist/protocol/parent.js +12 -0
  107. package/dist/protocol/protocolVersion.d.ts +6 -0
  108. package/dist/protocol/protocolVersion.js +6 -0
  109. package/dist/protocol/theme.d.ts +3 -0
  110. package/dist/protocol/theme.js +2 -0
  111. package/dist/protocol/users/user.d.ts +32 -0
  112. package/dist/protocol/users/user.js +20 -0
  113. package/dist/queries/querySubscriptions.d.ts +17 -0
  114. package/dist/queries/querySubscriptions.d.ts.map +1 -0
  115. package/dist/queries/querySubscriptions.js +38 -0
  116. package/dist/queries/types.d.ts +4 -0
  117. package/dist/queries/types.d.ts.map +1 -0
  118. package/dist/queries/types.js +1 -0
  119. package/dist/queries/validateQueryDataSourceFilter.d.ts +5 -0
  120. package/dist/queries/validateQueryDataSourceFilter.d.ts.map +1 -0
  121. package/dist/queries/validateQueryDataSourceFilter.js +72 -0
  122. package/dist/queries/validateQueryDataSourceSorts.d.ts +8 -0
  123. package/dist/queries/validateQueryDataSourceSorts.d.ts.map +1 -0
  124. package/dist/queries/validateQueryDataSourceSorts.js +47 -0
  125. package/dist/utils.d.ts +5 -0
  126. package/dist/utils.d.ts.map +1 -0
  127. package/dist/utils.js +6 -0
  128. package/package.json +26 -0
  129. package/src/autoBindProperties.ts +63 -0
  130. package/src/createCustomBlockHost.ts +605 -0
  131. package/src/index.ts +25 -0
  132. package/src/lifecycle/initErrors.ts +165 -0
  133. package/src/lifecycle/initializationController.ts +290 -0
  134. package/src/lifecycle/protocolVersion.ts +3 -0
  135. package/src/lifecycle/types.ts +40 -0
  136. package/src/messages/invalidSandboxMessage.ts +35 -0
  137. package/src/messages/types.ts +80 -0
  138. package/src/queries/querySubscriptions.ts +57 -0
  139. package/src/queries/types.ts +10 -0
  140. package/src/queries/validateQueryDataSourceFilter.ts +101 -0
  141. package/src/queries/validateQueryDataSourceSorts.ts +69 -0
  142. 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,3 @@
1
+ import * as v from "valibot";
2
+ export declare const notionThemeSchema: v.PicklistSchema<["light", "dark"], undefined>;
3
+ export type NotionTheme = v.InferOutput<typeof notionThemeSchema>;
@@ -0,0 +1,2 @@
1
+ import * as v from "valibot";
2
+ export const notionThemeSchema = v.picklist(["light", "dark"]);
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Throws when a discriminated union has an unexpected member.
3
+ */
4
+ export declare function unreachable(value: never): never;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -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
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Throws when a discriminated union has an unexpected member.
3
+ */
4
+ export function unreachable(value) {
5
+ throw new Error(`[custom-blocks-host] Unexpected value encountered: ${JSON.stringify(value)}`);
6
+ }
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
+ }