ncblock 0.0.3 → 0.0.5
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/HOST.md +68 -0
- package/README.md +3 -0
- package/bridge/SandboxBridge.ts +659 -0
- package/bridge/context.ts +58 -0
- package/bridge/dataSources/dataSource.ts +63 -0
- package/bridge/dataSources/dataSourcePage.ts +69 -0
- package/bridge/dataSources/dataSourceValue.ts +19 -0
- package/bridge/dataSources/dateValue.ts +96 -0
- package/bridge/dataSources/propertySchema.ts +186 -0
- package/bridge/dataSources/recordPointer.ts +13 -0
- package/bridge/dataSources/resolve.ts +96 -0
- package/bridge/dataSources/resolveProperty.ts +96 -0
- package/bridge/hostState.ts +146 -0
- package/bridge/ids.ts +30 -0
- package/bridge/incomingType.ts +19 -0
- package/bridge/loadManifest.ts +54 -0
- package/bridge/manifest.ts +53 -0
- package/bridge/messages/contextChanged.ts +15 -0
- package/bridge/messages/createPage.ts +64 -0
- package/bridge/messages/createPageResult.ts +25 -0
- package/bridge/messages/dataSourcesChanged.ts +18 -0
- package/bridge/messages/getPage.ts +32 -0
- package/bridge/messages/getUser.ts +32 -0
- package/bridge/messages/hostToSandbox.ts +33 -0
- package/bridge/messages/init.ts +20 -0
- package/bridge/messages/invalidHostMessage.ts +16 -0
- package/bridge/messages/invalidSandboxMessage.ts +18 -0
- package/bridge/messages/listUsers.ts +33 -0
- package/bridge/messages/queryDataSource.ts +16 -0
- package/bridge/messages/queryDataSourceResult.ts +18 -0
- package/bridge/messages/ready.ts +25 -0
- package/bridge/messages/resize.ts +13 -0
- package/bridge/messages/sandboxToHost.ts +30 -0
- package/bridge/messages/themeChanged.ts +15 -0
- package/bridge/messages/updatePage.ts +21 -0
- package/bridge/messages/updatePageResult.ts +24 -0
- package/bridge/pages/page.ts +314 -0
- package/bridge/pendingRequests.ts +28 -0
- package/bridge/sandboxClient.ts +112 -0
- package/bridge/theme.ts +5 -0
- package/bridge/users/user.ts +31 -0
- package/docs/context.md +45 -0
- package/docs/data-sources.md +161 -0
- package/docs/lifecycle.md +92 -0
- package/docs/manifest.md +42 -0
- package/docs/pages.md +143 -0
- package/docs/users.md +61 -0
- package/host.ts +67 -0
- package/index.ts +86 -0
- package/init.ts +92 -0
- package/package.json +15 -5
- package/react.tsx +418 -0
- package/types.ts +157 -0
- package/users.ts +26 -0
- package/utils.ts +13 -0
- package/vite-plugin/index.d.ts +46 -0
- package/vite-plugin/index.js +115 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
import { notionDataSourceIdSchema, notionSpaceIdSchema } from "../ids"
|
|
3
|
+
import { notionPropertySchemaSchema } from "./propertySchema"
|
|
4
|
+
|
|
5
|
+
const collectionPointerValidator = v.object({
|
|
6
|
+
id: notionDataSourceIdSchema,
|
|
7
|
+
table: v.string(),
|
|
8
|
+
spaceId: v.optional(notionSpaceIdSchema),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export type NotionCollectionPointer = v.InferOutput<
|
|
12
|
+
typeof collectionPointerValidator
|
|
13
|
+
>
|
|
14
|
+
|
|
15
|
+
export const notionCollectionSchemaValidator = v.object({
|
|
16
|
+
id: v.optional(notionDataSourceIdSchema),
|
|
17
|
+
propertiesById: v.record(v.string(), notionPropertySchemaSchema),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export type NotionCollectionSchema = v.InferOutput<
|
|
21
|
+
typeof notionCollectionSchemaValidator
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bridge shape for a single data source: a semantic key bound (optionally) to a collection,
|
|
26
|
+
* its property name → ID mapping, and its column schemas.
|
|
27
|
+
*/
|
|
28
|
+
export const notionDataSourceSchema = v.object({
|
|
29
|
+
key: v.string(),
|
|
30
|
+
collectionPointer: v.optional(collectionPointerValidator),
|
|
31
|
+
collectionSchema: v.optional(notionCollectionSchemaValidator),
|
|
32
|
+
/**
|
|
33
|
+
* Keyed mapping of user-defined keys to a raw property ID. A key mapped to `undefined`
|
|
34
|
+
* is a declared-but-unbound slot.
|
|
35
|
+
*/
|
|
36
|
+
propertyIdsByKey: v.record(v.string(), v.optional(v.string())),
|
|
37
|
+
/**
|
|
38
|
+
* Keyed mapping of raw property IDs to their schema. Always includes the four built-ins
|
|
39
|
+
* (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`).
|
|
40
|
+
*/
|
|
41
|
+
propertySchemasById: v.record(v.string(), notionPropertySchemaSchema),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export type NotionDataSource = v.InferOutput<typeof notionDataSourceSchema>
|
|
45
|
+
|
|
46
|
+
export const notionDataSourceBindingSchema = v.object({
|
|
47
|
+
collectionPointer: v.optional(collectionPointerValidator),
|
|
48
|
+
collectionSchema: v.optional(notionCollectionSchemaValidator),
|
|
49
|
+
propertyIdsByKey: v.optional(v.record(v.string(), v.optional(v.string()))),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export type NotionDataSourceBinding = v.InferOutput<
|
|
53
|
+
typeof notionDataSourceBindingSchema
|
|
54
|
+
>
|
|
55
|
+
|
|
56
|
+
export const notionDataSourceBindingsSchema = v.record(
|
|
57
|
+
v.string(),
|
|
58
|
+
notionDataSourceBindingSchema,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
export type NotionDataSourceBindings = v.InferOutput<
|
|
62
|
+
typeof notionDataSourceBindingsSchema
|
|
63
|
+
>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
import type {
|
|
3
|
+
NotionPage,
|
|
4
|
+
NotionPageCover,
|
|
5
|
+
NotionPageIcon,
|
|
6
|
+
NotionPageId,
|
|
7
|
+
NotionPagePropertyInputMap,
|
|
8
|
+
} from "../pages/page"
|
|
9
|
+
import {
|
|
10
|
+
type NotionDataSourceValue,
|
|
11
|
+
notionDataSourceValueSchema,
|
|
12
|
+
} from "./dataSourceValue"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bridge shape for a data source page, as parsed from inbound host messages. The host keys
|
|
16
|
+
* properties by raw property ID. The four built-ins (`created_time`, `last_edited_time`,
|
|
17
|
+
* `created_by`, `last_edited_by`) are normally present, possibly with `undefined` values.
|
|
18
|
+
*
|
|
19
|
+
* Internal to the SDK. The consumer-facing shape is {@link NotionDataSourcePage}.
|
|
20
|
+
*/
|
|
21
|
+
export const notionDataSourcePageBridgeSchema = v.object({
|
|
22
|
+
id: v.string(),
|
|
23
|
+
propertiesById: v.record(v.string(), v.optional(notionDataSourceValueSchema)),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/** Internal SDK type for a parsed bridge page. */
|
|
27
|
+
export type NotionDataSourcePageBridge = v.InferOutput<
|
|
28
|
+
typeof notionDataSourcePageBridgeSchema
|
|
29
|
+
>
|
|
30
|
+
|
|
31
|
+
export type NotionDataSourcePageUpdateInput = {
|
|
32
|
+
properties?: NotionPagePropertyInputMap
|
|
33
|
+
icon?: NotionPageIcon
|
|
34
|
+
cover?: NotionPageCover
|
|
35
|
+
archived?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type NotionDataSourcePageUpdateResult =
|
|
39
|
+
| {
|
|
40
|
+
status: "success"
|
|
41
|
+
page: NotionPage
|
|
42
|
+
}
|
|
43
|
+
| { status: "error"; error: string }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Consumer-facing page shape returned from {@link useDataSource}. Derived from the bridge payload
|
|
47
|
+
* plus the data source's `propertyIdsByKey`.
|
|
48
|
+
*/
|
|
49
|
+
export type NotionDataSourcePage = {
|
|
50
|
+
id: NotionPageId
|
|
51
|
+
/**
|
|
52
|
+
* Keyed mapping of raw property IDs to their value. Includes all properties on the data source,
|
|
53
|
+
* including the four built-ins (`created_time`, `last_edited_time`, `created_by`,
|
|
54
|
+
* `last_edited_by`).
|
|
55
|
+
*/
|
|
56
|
+
propertiesById: { [propertyId: string]: NotionDataSourceValue | undefined }
|
|
57
|
+
/**
|
|
58
|
+
* Keyed mapping of user-defined keys to their value. Only includes properties that are mapped
|
|
59
|
+
* to a raw property ID in the data source's `propertyIdsByKey`.
|
|
60
|
+
*/
|
|
61
|
+
propertiesByKey: { [key: string]: NotionDataSourceValue | undefined }
|
|
62
|
+
/**
|
|
63
|
+
* Updates this page using the data source's property-key bindings. The SDK resolves property
|
|
64
|
+
* keys to raw property IDs before sending the bridge message to the host.
|
|
65
|
+
*/
|
|
66
|
+
update: (
|
|
67
|
+
input: NotionDataSourcePageUpdateInput,
|
|
68
|
+
) => Promise<NotionDataSourcePageUpdateResult>
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
import { notionDateValueSchema } from "./dateValue"
|
|
3
|
+
import { notionRecordPointerSchema } from "./recordPointer"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Possible types for a data source value.
|
|
7
|
+
*/
|
|
8
|
+
export const notionDataSourceValueSchema = v.union([
|
|
9
|
+
v.string(),
|
|
10
|
+
v.number(),
|
|
11
|
+
v.boolean(),
|
|
12
|
+
notionDateValueSchema,
|
|
13
|
+
v.array(v.string()),
|
|
14
|
+
v.array(notionRecordPointerSchema),
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
export type NotionDataSourceValue = v.InferOutput<
|
|
18
|
+
typeof notionDataSourceValueSchema
|
|
19
|
+
>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
|
|
3
|
+
export const notionDateReminderSchema = v.object({
|
|
4
|
+
unit: v.picklist(["year", "month", "week", "day"]),
|
|
5
|
+
value: v.number(),
|
|
6
|
+
time: v.string(),
|
|
7
|
+
defaultTimeZone: v.optional(v.string()),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export type NotionDateReminder = v.InferOutput<typeof notionDateReminderSchema>
|
|
11
|
+
|
|
12
|
+
export const notionTimeReminderSchema = v.object({
|
|
13
|
+
unit: v.picklist(["hour", "minute"]),
|
|
14
|
+
value: v.number(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export type NotionTimeReminder = v.InferOutput<typeof notionTimeReminderSchema>
|
|
18
|
+
|
|
19
|
+
export const notionNoReminderSchema = v.object({
|
|
20
|
+
unit: v.literal("none"),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export type NotionNoReminder = v.InferOutput<typeof notionNoReminderSchema>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Possible types for a Notion `datetime` reminder.
|
|
27
|
+
*/
|
|
28
|
+
export const notionDateTimeReminderSchema = v.union([
|
|
29
|
+
notionDateReminderSchema,
|
|
30
|
+
notionTimeReminderSchema,
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
export type NotionDateTimeReminder = v.InferOutput<
|
|
34
|
+
typeof notionDateTimeReminderSchema
|
|
35
|
+
>
|
|
36
|
+
|
|
37
|
+
const dateOrNoReminder = v.optional(
|
|
38
|
+
v.union([notionDateReminderSchema, notionNoReminderSchema]),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const dateTimeOrNoReminder = v.optional(
|
|
42
|
+
v.union([notionDateTimeReminderSchema, notionNoReminderSchema]),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const notionDateSchema = v.object({
|
|
46
|
+
type: v.literal("date"),
|
|
47
|
+
start_date: v.string(),
|
|
48
|
+
reminder: dateOrNoReminder,
|
|
49
|
+
})
|
|
50
|
+
export type NotionDate = v.InferOutput<typeof notionDateSchema>
|
|
51
|
+
|
|
52
|
+
export const notionDateRangeSchema = v.object({
|
|
53
|
+
type: v.literal("daterange"),
|
|
54
|
+
start_date: v.string(),
|
|
55
|
+
end_date: v.string(),
|
|
56
|
+
reminder: dateOrNoReminder,
|
|
57
|
+
})
|
|
58
|
+
export type NotionDateRange = v.InferOutput<typeof notionDateRangeSchema>
|
|
59
|
+
|
|
60
|
+
export const notionDateTimeSchema = v.object({
|
|
61
|
+
type: v.literal("datetime"),
|
|
62
|
+
start_date: v.string(),
|
|
63
|
+
start_time: v.string(),
|
|
64
|
+
time_zone: v.string(),
|
|
65
|
+
reminder: dateTimeOrNoReminder,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export type NotionDateTime = v.InferOutput<typeof notionDateTimeSchema>
|
|
69
|
+
|
|
70
|
+
export const notionDateTimeRangeSchema = v.object({
|
|
71
|
+
type: v.literal("datetimerange"),
|
|
72
|
+
start_date: v.string(),
|
|
73
|
+
start_time: v.string(),
|
|
74
|
+
end_date: v.string(),
|
|
75
|
+
end_time: v.string(),
|
|
76
|
+
time_zone: v.string(),
|
|
77
|
+
reminder: dateTimeOrNoReminder,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
export type NotionDateTimeRange = v.InferOutput<
|
|
81
|
+
typeof notionDateTimeRangeSchema
|
|
82
|
+
>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Possible types for a Notion `date` value.
|
|
86
|
+
*/
|
|
87
|
+
export const notionDateValueSchema = v.variant("type", [
|
|
88
|
+
notionDateSchema,
|
|
89
|
+
notionDateRangeSchema,
|
|
90
|
+
notionDateTimeSchema,
|
|
91
|
+
notionDateTimeRangeSchema,
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
export type NotionDateValue = Readonly<
|
|
95
|
+
v.InferOutput<typeof notionDateValueSchema>
|
|
96
|
+
>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hex-token identifiers for Notion's named colors. Mirrors the public API's
|
|
5
|
+
* `select.options[].color` enum
|
|
6
|
+
* (https://developers.notion.com/reference/property-object#select).
|
|
7
|
+
*/
|
|
8
|
+
export const notionPropertyColorSchema = v.picklist([
|
|
9
|
+
"default",
|
|
10
|
+
"gray",
|
|
11
|
+
"brown",
|
|
12
|
+
"orange",
|
|
13
|
+
"yellow",
|
|
14
|
+
"green",
|
|
15
|
+
"blue",
|
|
16
|
+
"purple",
|
|
17
|
+
"pink",
|
|
18
|
+
"red",
|
|
19
|
+
"gray_background",
|
|
20
|
+
"brown_background",
|
|
21
|
+
"orange_background",
|
|
22
|
+
"yellow_background",
|
|
23
|
+
"green_background",
|
|
24
|
+
"blue_background",
|
|
25
|
+
"purple_background",
|
|
26
|
+
"pink_background",
|
|
27
|
+
"red_background",
|
|
28
|
+
"default_background",
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
export type NotionPropertyColor = v.InferOutput<
|
|
32
|
+
typeof notionPropertyColorSchema
|
|
33
|
+
>
|
|
34
|
+
|
|
35
|
+
export const notionPropertyOptionSchema = v.object({
|
|
36
|
+
id: v.string(),
|
|
37
|
+
name: v.string(),
|
|
38
|
+
color: v.optional(notionPropertyColorSchema),
|
|
39
|
+
description: v.optional(v.string()),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export type NotionPropertyOption = v.InferOutput<
|
|
43
|
+
typeof notionPropertyOptionSchema
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
export const notionStatusGroupSchema = v.object({
|
|
47
|
+
id: v.string(),
|
|
48
|
+
name: v.string(),
|
|
49
|
+
color: v.optional(notionPropertyColorSchema),
|
|
50
|
+
option_ids: v.array(v.string()),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export type NotionStatusGroup = v.InferOutput<typeof notionStatusGroupSchema>
|
|
54
|
+
|
|
55
|
+
export const notionDualPropertySchema = v.object({
|
|
56
|
+
synced_property_id: v.string(),
|
|
57
|
+
synced_property_name: v.string(),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export type NotionDualProperty = v.InferOutput<typeof notionDualPropertySchema>
|
|
61
|
+
|
|
62
|
+
const baseProp = v.object({
|
|
63
|
+
name: v.string(),
|
|
64
|
+
description: v.optional(v.string()),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Every Notion property type the bridge speaks, in a single readable list.
|
|
69
|
+
* Mirrors the Notion public API
|
|
70
|
+
* [property object](https://developers.notion.com/reference/property-object)
|
|
71
|
+
* type field. Internal-only types (`button`, `verification`,
|
|
72
|
+
* `last_visited_time`, `location`) and the four built-ins (`created_time`,
|
|
73
|
+
* `last_edited_time`, `created_by`, `last_edited_by`) are included under their
|
|
74
|
+
* bridge-native names.
|
|
75
|
+
*/
|
|
76
|
+
export const NOTION_PROPERTY_TYPES = [
|
|
77
|
+
"title",
|
|
78
|
+
"rich_text",
|
|
79
|
+
"number",
|
|
80
|
+
"checkbox",
|
|
81
|
+
"url",
|
|
82
|
+
"email",
|
|
83
|
+
"phone_number",
|
|
84
|
+
"select",
|
|
85
|
+
"multi_select",
|
|
86
|
+
"status",
|
|
87
|
+
"date",
|
|
88
|
+
"people",
|
|
89
|
+
"files",
|
|
90
|
+
"unique_id",
|
|
91
|
+
"relation",
|
|
92
|
+
"place",
|
|
93
|
+
"formula",
|
|
94
|
+
"rollup",
|
|
95
|
+
"button",
|
|
96
|
+
"verification",
|
|
97
|
+
"last_visited_time",
|
|
98
|
+
"location",
|
|
99
|
+
"created_time",
|
|
100
|
+
"last_edited_time",
|
|
101
|
+
"created_by",
|
|
102
|
+
"last_edited_by",
|
|
103
|
+
] as const
|
|
104
|
+
|
|
105
|
+
export const notionPropertyTypeSchema = v.picklist(NOTION_PROPERTY_TYPES)
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* String literal union of every supported Notion property type. Same set as
|
|
109
|
+
* the discriminator in `NotionPropertySchema` — split out so the manifest
|
|
110
|
+
* can declare expected property types without dragging in the per-type
|
|
111
|
+
* payload shapes.
|
|
112
|
+
*/
|
|
113
|
+
export type NotionPropertyType = v.InferOutput<typeof notionPropertyTypeSchema>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Per-property schema as exposed by the host over the custom-block bridge.
|
|
117
|
+
* The `type` discriminator must be one of {@link NOTION_PROPERTY_TYPES}.
|
|
118
|
+
*/
|
|
119
|
+
export const notionPropertySchemaSchema = v.variant("type", [
|
|
120
|
+
v.object({ ...baseProp.entries, type: v.literal("title") }),
|
|
121
|
+
v.object({ ...baseProp.entries, type: v.literal("rich_text") }),
|
|
122
|
+
v.object({ ...baseProp.entries, type: v.literal("number") }),
|
|
123
|
+
v.object({ ...baseProp.entries, type: v.literal("checkbox") }),
|
|
124
|
+
v.object({ ...baseProp.entries, type: v.literal("url") }),
|
|
125
|
+
v.object({ ...baseProp.entries, type: v.literal("email") }),
|
|
126
|
+
v.object({ ...baseProp.entries, type: v.literal("phone_number") }),
|
|
127
|
+
v.object({
|
|
128
|
+
...baseProp.entries,
|
|
129
|
+
type: v.literal("select"),
|
|
130
|
+
options: v.array(notionPropertyOptionSchema),
|
|
131
|
+
}),
|
|
132
|
+
v.object({
|
|
133
|
+
...baseProp.entries,
|
|
134
|
+
type: v.literal("multi_select"),
|
|
135
|
+
options: v.array(notionPropertyOptionSchema),
|
|
136
|
+
}),
|
|
137
|
+
v.object({
|
|
138
|
+
...baseProp.entries,
|
|
139
|
+
type: v.literal("status"),
|
|
140
|
+
options: v.array(notionPropertyOptionSchema),
|
|
141
|
+
groups: v.array(notionStatusGroupSchema),
|
|
142
|
+
}),
|
|
143
|
+
v.object({ ...baseProp.entries, type: v.literal("date") }),
|
|
144
|
+
v.object({ ...baseProp.entries, type: v.literal("people") }),
|
|
145
|
+
v.object({ ...baseProp.entries, type: v.literal("files") }),
|
|
146
|
+
v.object({ ...baseProp.entries, type: v.literal("unique_id") }),
|
|
147
|
+
v.object({
|
|
148
|
+
...baseProp.entries,
|
|
149
|
+
type: v.literal("relation"),
|
|
150
|
+
data_source_id: v.optional(v.string()),
|
|
151
|
+
dual_property: v.optional(notionDualPropertySchema),
|
|
152
|
+
}),
|
|
153
|
+
v.object({ ...baseProp.entries, type: v.literal("place") }),
|
|
154
|
+
v.object({ ...baseProp.entries, type: v.literal("formula") }),
|
|
155
|
+
v.object({ ...baseProp.entries, type: v.literal("rollup") }),
|
|
156
|
+
|
|
157
|
+
// Internal-only types passed through under their bridge-native names.
|
|
158
|
+
v.object({ ...baseProp.entries, type: v.literal("button") }),
|
|
159
|
+
v.object({ ...baseProp.entries, type: v.literal("verification") }),
|
|
160
|
+
v.object({ ...baseProp.entries, type: v.literal("last_visited_time") }),
|
|
161
|
+
v.object({ ...baseProp.entries, type: v.literal("location") }),
|
|
162
|
+
|
|
163
|
+
// Synthetic built-ins. The host always emits one of each per data source.
|
|
164
|
+
v.object({ ...baseProp.entries, type: v.literal("created_time") }),
|
|
165
|
+
v.object({ ...baseProp.entries, type: v.literal("last_edited_time") }),
|
|
166
|
+
v.object({ ...baseProp.entries, type: v.literal("created_by") }),
|
|
167
|
+
v.object({ ...baseProp.entries, type: v.literal("last_edited_by") }),
|
|
168
|
+
])
|
|
169
|
+
|
|
170
|
+
export type NotionPropertySchema = v.InferOutput<
|
|
171
|
+
typeof notionPropertySchemaSchema
|
|
172
|
+
>
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The four synthetic built-in property IDs the host always includes in every
|
|
176
|
+
* data source's `propertySchemasById` and every row's `propertiesById`.
|
|
177
|
+
*/
|
|
178
|
+
export const NOTION_BUILTIN_PROPERTY_IDS = [
|
|
179
|
+
"created_time",
|
|
180
|
+
"last_edited_time",
|
|
181
|
+
"created_by",
|
|
182
|
+
"last_edited_by",
|
|
183
|
+
] as const
|
|
184
|
+
|
|
185
|
+
export type NotionBuiltinPropertyId =
|
|
186
|
+
(typeof NOTION_BUILTIN_PROPERTY_IDS)[number]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A pointer to a record in a database.
|
|
5
|
+
*/
|
|
6
|
+
export const notionRecordPointerSchema = v.object({
|
|
7
|
+
id: v.string(),
|
|
8
|
+
table: v.string(),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export type NotionRecordPointer = v.InferOutput<
|
|
12
|
+
typeof notionRecordPointerSchema
|
|
13
|
+
>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CustomBlockManifest } from "../manifest"
|
|
2
|
+
import type { NotionDataSource, NotionDataSourceBindings } from "./dataSource"
|
|
3
|
+
import type { NotionPropertySchema } from "./propertySchema"
|
|
4
|
+
|
|
5
|
+
type ResolveDataSourcesArgs = {
|
|
6
|
+
manifest: CustomBlockManifest | null
|
|
7
|
+
dataSourceBindings: NotionDataSourceBindings
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Builds the public {@link NotionDataSource} list the SDK exposes to consumers.
|
|
12
|
+
*
|
|
13
|
+
* Combines the host-supplied bindings (collection pointers + schemas) with the
|
|
14
|
+
* manifest's declared data-source keys. When the manifest names a property
|
|
15
|
+
* that isn't in `propertyIdsByKey`, falls back to {@link findPropertyIdByManifestProperty}
|
|
16
|
+
* so renames in Notion still resolve as long as the schema name matches.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveDataSources(
|
|
19
|
+
args: ResolveDataSourcesArgs,
|
|
20
|
+
): NotionDataSource[] {
|
|
21
|
+
if (args.manifest === null) {
|
|
22
|
+
return Object.entries(args.dataSourceBindings).map(([key, binding]) => ({
|
|
23
|
+
key,
|
|
24
|
+
collectionPointer: binding.collectionPointer,
|
|
25
|
+
collectionSchema: binding.collectionSchema,
|
|
26
|
+
propertyIdsByKey: { ...(binding.propertyIdsByKey ?? {}) },
|
|
27
|
+
propertySchemasById: binding.collectionSchema?.propertiesById ?? {},
|
|
28
|
+
}))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Object.entries(args.manifest.dataSources).map(
|
|
32
|
+
([key, manifestDataSource]) => {
|
|
33
|
+
const binding = args.dataSourceBindings[key]
|
|
34
|
+
const propertySchemasById =
|
|
35
|
+
binding?.collectionSchema?.propertiesById ?? {}
|
|
36
|
+
|
|
37
|
+
const bindingPropertyIdsByKey = binding?.propertyIdsByKey ?? {}
|
|
38
|
+
const propertyIdsByKey: Record<string, string | undefined> = {}
|
|
39
|
+
const manifestProperties = Object.entries(
|
|
40
|
+
manifestDataSource.properties ?? {},
|
|
41
|
+
)
|
|
42
|
+
for (const [propertyKey, manifestProperty] of manifestProperties) {
|
|
43
|
+
const propertyId =
|
|
44
|
+
propertyKey in bindingPropertyIdsByKey
|
|
45
|
+
? bindingPropertyIdsByKey[propertyKey]
|
|
46
|
+
: findPropertyIdByManifestProperty(propertySchemasById, {
|
|
47
|
+
key: propertyKey,
|
|
48
|
+
type: manifestProperty.type,
|
|
49
|
+
})
|
|
50
|
+
const propertySchema =
|
|
51
|
+
propertyId === undefined ? undefined : propertySchemasById[propertyId]
|
|
52
|
+
propertyIdsByKey[propertyKey] =
|
|
53
|
+
propertySchema?.type === manifestProperty.type
|
|
54
|
+
? propertyId
|
|
55
|
+
: undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
key,
|
|
60
|
+
collectionPointer: binding?.collectionPointer,
|
|
61
|
+
collectionSchema: binding?.collectionSchema,
|
|
62
|
+
propertyIdsByKey,
|
|
63
|
+
propertySchemasById,
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function findPropertyIdByManifestProperty(
|
|
70
|
+
propertySchemasById: Record<string, NotionPropertySchema>,
|
|
71
|
+
manifestProperty: { key: string; type: NotionPropertySchema["type"] },
|
|
72
|
+
): string | undefined {
|
|
73
|
+
// `ManifestProperty.name` is display copy for setup UI. Binding fallback is based
|
|
74
|
+
// on the stable semantic key so copy changes don't affect property resolution.
|
|
75
|
+
const propertySchemaForKey = propertySchemasById[manifestProperty.key]
|
|
76
|
+
if (propertySchemaForKey?.type === manifestProperty.type) {
|
|
77
|
+
return manifestProperty.key
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const normalizedKey = normalizePropertyName(manifestProperty.key)
|
|
81
|
+
for (const [propertyId, propertySchema] of Object.entries(
|
|
82
|
+
propertySchemasById,
|
|
83
|
+
)) {
|
|
84
|
+
if (
|
|
85
|
+
propertySchema.type === manifestProperty.type &&
|
|
86
|
+
normalizePropertyName(propertySchema.name) === normalizedKey
|
|
87
|
+
) {
|
|
88
|
+
return propertyId
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizePropertyName(value: string): string {
|
|
95
|
+
return value.trim().toLowerCase()
|
|
96
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as v from "valibot"
|
|
2
|
+
import type {
|
|
3
|
+
NotionPagePropertyInputMap,
|
|
4
|
+
NotionPagePropertyWriteMap,
|
|
5
|
+
} from "../pages/page"
|
|
6
|
+
import { notionPagePropertyValueSchema } from "../pages/page"
|
|
7
|
+
import type { NotionDataSource } from "./dataSource"
|
|
8
|
+
|
|
9
|
+
export type PropertyWriteMapResolutionResult =
|
|
10
|
+
| { status: "success"; properties: NotionPagePropertyWriteMap }
|
|
11
|
+
| { status: "error"; error: string }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a public SDK property write map into the ID-keyed bridge shape.
|
|
15
|
+
*
|
|
16
|
+
* Identifiers in `properties` may be raw property IDs or data-source property keys; the latter
|
|
17
|
+
* are looked up in the `dataSource`'s `propertyIdsByKey`. Each value is re-parsed through
|
|
18
|
+
* `notionPagePropertyValueSchema` with its `id` rewritten to the resolved property ID.
|
|
19
|
+
*/
|
|
20
|
+
export function resolvePropertyWriteMapForDataSource(args: {
|
|
21
|
+
dataSource: NotionDataSource | undefined
|
|
22
|
+
properties: NotionPagePropertyInputMap
|
|
23
|
+
operationName: string
|
|
24
|
+
}): PropertyWriteMapResolutionResult {
|
|
25
|
+
const { dataSource, properties, operationName } = args
|
|
26
|
+
|
|
27
|
+
const resolvedProperties: NotionPagePropertyWriteMap = {}
|
|
28
|
+
for (const [identifier, value] of Object.entries(properties)) {
|
|
29
|
+
const propertyIdResult = resolvePropertyIdentifierForDataSource({
|
|
30
|
+
dataSource,
|
|
31
|
+
identifier,
|
|
32
|
+
operationName,
|
|
33
|
+
})
|
|
34
|
+
if (propertyIdResult.status === "error") {
|
|
35
|
+
return propertyIdResult
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const propertyId = propertyIdResult.propertyId
|
|
39
|
+
if (
|
|
40
|
+
value.id !== undefined &&
|
|
41
|
+
value.id !== identifier &&
|
|
42
|
+
value.id !== propertyId
|
|
43
|
+
) {
|
|
44
|
+
return {
|
|
45
|
+
status: "error",
|
|
46
|
+
error: `Property ${identifier} resolved to ${propertyId} but value id was ${value.id}.`,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (resolvedProperties[propertyId] !== undefined) {
|
|
50
|
+
return {
|
|
51
|
+
status: "error",
|
|
52
|
+
error: `Cannot set property ${propertyId} more than once.`,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parsedValue = v.safeParse(notionPagePropertyValueSchema, {
|
|
57
|
+
...value,
|
|
58
|
+
id: propertyId,
|
|
59
|
+
})
|
|
60
|
+
if (!parsedValue.success) {
|
|
61
|
+
return {
|
|
62
|
+
status: "error",
|
|
63
|
+
error: `Invalid value for property ${identifier}.`,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
resolvedProperties[propertyId] = parsedValue.output
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { status: "success", properties: resolvedProperties }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Treats matching data-source keys as aliases for property IDs; all other identifiers are raw IDs.
|
|
75
|
+
*/
|
|
76
|
+
export function resolvePropertyIdentifierForDataSource(args: {
|
|
77
|
+
dataSource: NotionDataSource | undefined
|
|
78
|
+
identifier: string
|
|
79
|
+
operationName: string
|
|
80
|
+
}):
|
|
81
|
+
| { status: "success"; propertyId: string }
|
|
82
|
+
| { status: "error"; error: string } {
|
|
83
|
+
const { dataSource, identifier, operationName } = args
|
|
84
|
+
if (dataSource !== undefined && identifier in dataSource.propertyIdsByKey) {
|
|
85
|
+
const propertyId = dataSource.propertyIdsByKey[identifier]
|
|
86
|
+
if (propertyId === undefined) {
|
|
87
|
+
return {
|
|
88
|
+
status: "error",
|
|
89
|
+
error: `${operationName} cannot resolve property key "${identifier}" because it is not bound to a Notion property.`,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { status: "success", propertyId }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { status: "success", propertyId: identifier }
|
|
96
|
+
}
|