@notionhq/custom-blocks-host 0.3.4 → 0.3.6

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 (40) hide show
  1. package/README.md +0 -5
  2. package/dist/browser.js +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/lifecycle/legacyPage.d.ts +70 -0
  6. package/dist/lifecycle/legacyPage.d.ts.map +1 -0
  7. package/dist/lifecycle/legacyPage.js +171 -0
  8. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts +2 -2
  9. package/dist/lifecycle/normalizeHostToSandboxMessage.d.ts.map +1 -1
  10. package/dist/lifecycle/normalizeHostToSandboxMessage.js +8 -30
  11. package/dist/lifecycle/normalizePage.d.ts +7 -0
  12. package/dist/lifecycle/normalizePage.d.ts.map +1 -0
  13. package/dist/lifecycle/normalizePage.js +22 -0
  14. package/dist/messages/types.d.ts +1 -2
  15. package/dist/messages/types.d.ts.map +1 -1
  16. package/dist/protocol/dataSources/dataSourcePage.d.ts +1 -162
  17. package/dist/protocol/dataSources/dataSourcePage.js +1 -9
  18. package/dist/protocol/messages/createPage.js +2 -2
  19. package/dist/protocol/messages/createPageResult.d.ts +468 -1
  20. package/dist/protocol/messages/createPageResult.js +17 -2
  21. package/dist/protocol/messages/getPage.d.ts +468 -1
  22. package/dist/protocol/messages/getPage.js +18 -3
  23. package/dist/protocol/messages/hostToSandbox.d.ts +2398 -159
  24. package/dist/protocol/messages/hostToSandbox.js +22 -12
  25. package/dist/protocol/messages/queryDataSourceResult.d.ts +312 -2
  26. package/dist/protocol/messages/queryDataSourceResult.js +18 -5
  27. package/dist/protocol/messages/sandboxToHost.d.ts +2 -0
  28. package/dist/protocol/messages/updatePage.d.ts +1 -0
  29. package/dist/protocol/messages/updatePage.js +4 -3
  30. package/dist/protocol/messages/updatePageResult.d.ts +467 -1
  31. package/dist/protocol/messages/updatePageResult.js +16 -2
  32. package/dist/protocol/pages/page.d.ts +386 -6
  33. package/dist/protocol/pages/page.js +23 -2
  34. package/dist/protocol/protocolVersion.js +1 -1
  35. package/package.json +1 -1
  36. package/src/index.ts +2 -4
  37. package/src/lifecycle/legacyPage.ts +208 -0
  38. package/src/lifecycle/normalizeHostToSandboxMessage.ts +18 -35
  39. package/src/lifecycle/normalizePage.ts +35 -0
  40. package/src/messages/types.ts +1 -1
@@ -0,0 +1,208 @@
1
+ import type { NotionDataSourceValue } from "@notionhq/custom-blocks-protocol/dataSources/dataSourceValue.js"
2
+ import type {
3
+ LegacyNotionPage,
4
+ NotionPagePropertyValue,
5
+ notionPageDataSchema,
6
+ } from "@notionhq/custom-blocks-protocol/pages/page.js"
7
+ import type { InferOutput } from "valibot"
8
+ import { unreachable } from "../utils.js"
9
+
10
+ export function toLegacyPage(
11
+ page: InferOutput<typeof notionPageDataSchema>,
12
+ ): LegacyNotionPage {
13
+ return {
14
+ ...page,
15
+ properties: Object.fromEntries(
16
+ Object.values(page.properties).flatMap(property =>
17
+ property ? [[property.id, property]] : [],
18
+ ),
19
+ ),
20
+ icon: page.icon ?? undefined,
21
+ cover: page.cover ?? undefined,
22
+ created_time: page.created_time ?? undefined,
23
+ last_edited_time: page.last_edited_time ?? undefined,
24
+ url: page.url ?? undefined,
25
+ public_url: page.public_url ?? undefined,
26
+ }
27
+ }
28
+
29
+ export function toLegacyQueryPage(
30
+ page: InferOutput<typeof notionPageDataSchema>,
31
+ ) {
32
+ const propertiesById: Record<string, NotionDataSourceValue | undefined> = {}
33
+ for (const property of Object.values(page.properties)) {
34
+ if (property) {
35
+ propertiesById[property.id] = toLegacyProperty(property)
36
+ }
37
+ }
38
+ return {
39
+ id: page.id,
40
+ is_archived: page.is_archived,
41
+ in_trash: page.in_trash,
42
+ propertiesById,
43
+ }
44
+ }
45
+
46
+ function plainText(items: Record<string, unknown>[]): string {
47
+ return items
48
+ .map(item => {
49
+ if (typeof item.plain_text === "string") {
50
+ return item.plain_text
51
+ }
52
+ const text = item.text
53
+ return text &&
54
+ typeof text === "object" &&
55
+ "content" in text &&
56
+ typeof text.content === "string"
57
+ ? text.content
58
+ : ""
59
+ })
60
+ .join("")
61
+ }
62
+
63
+ function timestamp(
64
+ value: string | number | null | undefined,
65
+ ): NotionDataSourceValue | undefined {
66
+ if (value === undefined || value === null || value === "") {
67
+ return undefined
68
+ }
69
+ const date = new Date(value)
70
+ if (!Number.isFinite(date.getTime())) {
71
+ return undefined
72
+ }
73
+ return {
74
+ type: "datetime",
75
+ start_date: date.toISOString().slice(0, 10),
76
+ start_time: date.toISOString().slice(11, 16),
77
+ time_zone: "UTC",
78
+ }
79
+ }
80
+
81
+ function legacyDate(value: {
82
+ start: string
83
+ end?: string | null
84
+ time_zone?: string | null
85
+ }): NotionDataSourceValue {
86
+ const timed = value.start.includes("T") || value.time_zone != null
87
+ const offset = value.start.match(/([+-])(\d{2}):(\d{2})$/)
88
+ const offsetMinutes = offset
89
+ ? (Number(offset[2]) * 60 + Number(offset[3])) *
90
+ (offset[1] === "-" ? -1 : 1)
91
+ : 0
92
+ const parts = (input: string) => {
93
+ if (!input.includes("T")) {
94
+ return { date: input, time: "00:00" }
95
+ }
96
+ const wallClock = input.match(
97
+ /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::\d{2}(?:\.\d+)?)?$/,
98
+ )
99
+ if (wallClock) {
100
+ return { date: wallClock[1], time: wallClock[2] }
101
+ }
102
+ if (!value.time_zone) {
103
+ const local = new Date(
104
+ new Date(input).getTime() + offsetMinutes * 60_000,
105
+ ).toISOString()
106
+ return { date: local.slice(0, 10), time: local.slice(11, 16) }
107
+ }
108
+ const entries = new Intl.DateTimeFormat("en-CA", {
109
+ timeZone: value.time_zone,
110
+ year: "numeric",
111
+ month: "2-digit",
112
+ day: "2-digit",
113
+ hour: "2-digit",
114
+ minute: "2-digit",
115
+ hourCycle: "h23",
116
+ }).formatToParts(new Date(input))
117
+ const get = (type: string) =>
118
+ entries.find(part => part.type === type)?.value
119
+ return {
120
+ date: `${get("year")}-${get("month")}-${get("day")}`,
121
+ time: `${get("hour")}:${get("minute")}`,
122
+ }
123
+ }
124
+ const start = parts(value.start)
125
+ const end = value.end ? parts(value.end) : undefined
126
+ if (!timed) {
127
+ return end
128
+ ? { type: "daterange", start_date: start.date, end_date: end.date }
129
+ : { type: "date", start_date: start.date }
130
+ }
131
+ const zone =
132
+ value.time_zone ??
133
+ (offsetMinutes === 0
134
+ ? "UTC"
135
+ : `UTC${offsetMinutes < 0 ? "-" : "+"}${Math.floor(Math.abs(offsetMinutes) / 60)}${Math.abs(offsetMinutes) % 60 === 0 ? "" : `:${String(Math.abs(offsetMinutes) % 60).padStart(2, "0")}`}`)
136
+ return end
137
+ ? {
138
+ type: "datetimerange",
139
+ start_date: start.date,
140
+ start_time: start.time,
141
+ end_date: end.date,
142
+ end_time: end.time,
143
+ time_zone: zone,
144
+ }
145
+ : {
146
+ type: "datetime",
147
+ start_date: start.date,
148
+ start_time: start.time,
149
+ time_zone: zone,
150
+ }
151
+ }
152
+
153
+ function toLegacyProperty(
154
+ property: NotionPagePropertyValue,
155
+ ): NotionDataSourceValue | undefined {
156
+ switch (property.type) {
157
+ case "title":
158
+ return plainText(property.title)
159
+ case "rich_text":
160
+ return plainText(property.rich_text)
161
+ case "number":
162
+ return property.number ?? undefined
163
+ case "checkbox":
164
+ return property.checkbox
165
+ case "url":
166
+ return property.url ?? ""
167
+ case "email":
168
+ return property.email ?? ""
169
+ case "phone_number":
170
+ return property.phone_number ?? ""
171
+ case "select":
172
+ return property.select?.name
173
+ case "status":
174
+ return property.status?.name
175
+ case "multi_select":
176
+ return property.multi_select.flatMap(option => option.name ?? [])
177
+ case "date":
178
+ return property.date ? legacyDate(property.date) : undefined
179
+ case "people":
180
+ return property.people.map(person =>
181
+ person.object === "group"
182
+ ? {
183
+ id: person.id,
184
+ table: "space_permission_group",
185
+ spaceId: person.id.split("|")[1],
186
+ }
187
+ : { id: person.id, table: "notion_user" },
188
+ )
189
+ case "relation":
190
+ return property.relation.map(page => ({ id: page.id, table: "block" }))
191
+ case "files":
192
+ return property.files.map(file => file.name).join(",")
193
+ case "place":
194
+ return ""
195
+ case "created_time":
196
+ return timestamp(property.created_time)
197
+ case "last_edited_time":
198
+ return timestamp(property.last_edited_time)
199
+ case "last_visited_time":
200
+ return timestamp(property.last_visited_time)
201
+ case "created_by":
202
+ return property.created_by ? [property.created_by] : undefined
203
+ case "last_edited_by":
204
+ return property.last_edited_by ? [property.last_edited_by] : undefined
205
+ default:
206
+ return unreachable(property)
207
+ }
208
+ }
@@ -1,46 +1,29 @@
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"
1
+ import type {
2
+ HostToSandboxMessage,
3
+ HostToSandboxMessageLegacy,
4
+ } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
5
+ import { normalizeToLegacyPage } from "./normalizePage.js"
3
6
 
4
7
  /**
5
8
  * Normalizes an outgoing message for the bridge protocol version requested by the sandbox.
6
9
  */
7
10
  // TODO(custom-blocks): Remove this function when MIN_HOST_SUPPORTED_BRIDGE_PROTOCOL_VERSION
8
- // reaches 5.
11
+ // reaches 6.
9
12
  export function normalizeHostToSandboxMessage(
10
13
  message: HostToSandboxMessage,
11
14
  bridgeProtocolVersion: number,
12
- ): HostToSandboxMessage {
13
- // The latest SDK version does not need any normalization.
14
- if (bridgeProtocolVersion >= 5) {
15
+ ): HostToSandboxMessageLegacy {
16
+ if (
17
+ message.type !== "queryDataSourceResult" &&
18
+ message.type !== "getPageResult" &&
19
+ message.type !== "createPageResult" &&
20
+ message.type !== "updatePageResult"
21
+ ) {
15
22
  return message
16
23
  }
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
- }
24
+ const normalizedMessage = normalizeToLegacyPage(
25
+ message,
26
+ bridgeProtocolVersion,
27
+ )
28
+ return normalizedMessage
46
29
  }
@@ -0,0 +1,35 @@
1
+ import type { CreatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/createPageResult.js"
2
+ import type { GetPageResultMessage } from "@notionhq/custom-blocks-protocol/messages/getPage.js"
3
+ import type { HostToSandboxMessageLegacy } from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
4
+ import type { QueryDataSourceResultMessage } from "@notionhq/custom-blocks-protocol/messages/queryDataSourceResult.js"
5
+ import type { UpdatePageResultMessage } from "@notionhq/custom-blocks-protocol/messages/updatePageResult.js"
6
+ import { LEGACY_PROPERTY_SHAPE_PROTOCOL_VERSION } from "@notionhq/custom-blocks-protocol/protocolVersion.js"
7
+ import { unreachable } from "../utils.js"
8
+ import { toLegacyPage, toLegacyQueryPage } from "./legacyPage.js"
9
+
10
+ export function normalizeToLegacyPage(
11
+ message:
12
+ | CreatePageResultMessage
13
+ | GetPageResultMessage
14
+ | UpdatePageResultMessage
15
+ | QueryDataSourceResultMessage,
16
+ bridgeProtocolVersion: number,
17
+ ): HostToSandboxMessageLegacy {
18
+ // Forward up to date messages
19
+ if (bridgeProtocolVersion >= LEGACY_PROPERTY_SHAPE_PROTOCOL_VERSION) {
20
+ return message
21
+ }
22
+ if (message.status === "error") {
23
+ return message
24
+ }
25
+ switch (message.type) {
26
+ case "queryDataSourceResult":
27
+ return { ...message, items: message.items.map(toLegacyQueryPage) }
28
+ case "getPageResult":
29
+ case "createPageResult":
30
+ case "updatePageResult":
31
+ return { ...message, page: toLegacyPage(message.page) }
32
+ default:
33
+ return unreachable(message)
34
+ }
35
+ }
@@ -57,7 +57,7 @@ import type {
57
57
  import type { InferInput } from "valibot"
58
58
  import type { CustomBlockHostQueryDataSourceResult } from "../queries/types.js"
59
59
 
60
- type MaybePromise<T> = T | Promise<T>
60
+ export type MaybePromise<T> = T | Promise<T>
61
61
 
62
62
  /**
63
63
  * Host results use InferInput so adapters can return plain string IDs.