@notionhq/apps 0.0.19 → 0.0.21
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 +5 -1
- package/dist/notion-as-code/custom-agent.d.ts +75 -1
- package/dist/notion-as-code/custom-agent.d.ts.map +1 -1
- package/dist/notion-as-code/custom-agent.js +11 -0
- package/dist/notion-as-code/custom-agent.test.d.ts +2 -0
- package/dist/notion-as-code/custom-agent.test.d.ts.map +1 -0
- package/dist/notion-as-code/database.d.ts +5 -1
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +3 -1
- package/dist/notion-as-code/date.d.ts +3 -2
- package/dist/notion-as-code/date.d.ts.map +1 -1
- package/dist/notion-as-code/handles.d.ts +8 -11
- package/dist/notion-as-code/handles.d.ts.map +1 -1
- package/dist/notion-as-code/handles.js +8 -7
- package/dist/notion-as-code/index.d.ts +10 -13
- package/dist/notion-as-code/index.d.ts.map +1 -1
- package/dist/notion-as-code/intents.d.ts +133 -11
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/notion-as-code-types.test.d.ts +2 -0
- package/dist/notion-as-code/notion-as-code-types.test.d.ts.map +1 -0
- package/dist/notion-as-code/page.d.ts +51 -3
- package/dist/notion-as-code/page.d.ts.map +1 -1
- package/dist/notion-as-code/page.test.d.ts +2 -0
- package/dist/notion-as-code/page.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/skills/notion-as-code/SKILL.md +19 -9
- package/skills/workflow/SKILL.md +3 -1
- package/src/notion-as-code/custom-agent.test.ts +79 -0
- package/src/notion-as-code/custom-agent.ts +111 -2
- package/src/notion-as-code/database.test.ts +38 -0
- package/src/notion-as-code/database.ts +7 -0
- package/src/notion-as-code/date.ts +4 -2
- package/src/notion-as-code/handles.ts +16 -12
- package/src/notion-as-code/index.ts +49 -2
- package/src/notion-as-code/intents.ts +219 -24
- package/src/notion-as-code/notion-as-code-types.test.ts +167 -0
- package/src/notion-as-code/page.test.ts +59 -0
- package/src/notion-as-code/page.ts +62 -4
|
@@ -21,6 +21,14 @@ export type NotionAsCodeIcon =
|
|
|
21
21
|
| { type: "notion_icon"; description: string; color?: string }
|
|
22
22
|
| { type: "file"; resourceId: ResourceId };
|
|
23
23
|
|
|
24
|
+
/** A URL reference used as a page or database cover. */
|
|
25
|
+
export type PageCoverReference = {
|
|
26
|
+
type: "url";
|
|
27
|
+
url: string;
|
|
28
|
+
/** Vertical position from the top, where 0 is the top and 1 is the bottom. */
|
|
29
|
+
position?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
24
32
|
export type NotionAsCodeSelectColor =
|
|
25
33
|
| "default"
|
|
26
34
|
| "gray"
|
|
@@ -49,20 +57,102 @@ type PropertyBase = {
|
|
|
49
57
|
description?: string;
|
|
50
58
|
};
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
/** Rollup aggregations supported for every target property type. */
|
|
61
|
+
export type RollupBaseAggregationType =
|
|
62
|
+
| "count"
|
|
63
|
+
| "count_values"
|
|
64
|
+
| "unique"
|
|
65
|
+
| "empty"
|
|
66
|
+
| "not_empty"
|
|
67
|
+
| "percent_empty"
|
|
68
|
+
| "percent_not_empty"
|
|
69
|
+
| "show_unique";
|
|
70
|
+
|
|
71
|
+
/** Numeric-only rollup aggregations. */
|
|
72
|
+
export type RollupNumberAggregationType =
|
|
73
|
+
| RollupBaseAggregationType
|
|
74
|
+
| "sum"
|
|
75
|
+
| "average"
|
|
76
|
+
| "median"
|
|
77
|
+
| "min"
|
|
78
|
+
| "max"
|
|
79
|
+
| "range";
|
|
80
|
+
|
|
81
|
+
/** Date-only rollup aggregations. */
|
|
82
|
+
export type RollupDateAggregationType =
|
|
83
|
+
| RollupBaseAggregationType
|
|
84
|
+
| "earliest_date"
|
|
85
|
+
| "latest_date"
|
|
86
|
+
| "date_range";
|
|
87
|
+
|
|
88
|
+
export type RollupTargetPropertyType =
|
|
89
|
+
| "title"
|
|
90
|
+
| "text"
|
|
91
|
+
| "number"
|
|
92
|
+
| "select"
|
|
93
|
+
| "multi_select"
|
|
94
|
+
| "status"
|
|
95
|
+
| "date"
|
|
96
|
+
| "checkbox"
|
|
97
|
+
| "url"
|
|
98
|
+
| "email"
|
|
99
|
+
| "phone_number"
|
|
100
|
+
| "relation"
|
|
101
|
+
| "person"
|
|
102
|
+
| "created_time"
|
|
103
|
+
| "last_edited_time"
|
|
104
|
+
| "created_by"
|
|
105
|
+
| "last_edited_by";
|
|
106
|
+
|
|
107
|
+
type RollupTarget =
|
|
108
|
+
| {
|
|
109
|
+
targetPropertyType: "number";
|
|
110
|
+
aggregation?: RollupNumberAggregationType;
|
|
111
|
+
}
|
|
112
|
+
| {
|
|
113
|
+
targetPropertyType: "date" | "created_time" | "last_edited_time";
|
|
114
|
+
aggregation?: RollupDateAggregationType;
|
|
115
|
+
}
|
|
116
|
+
| {
|
|
117
|
+
targetPropertyType: Exclude<
|
|
118
|
+
RollupTargetPropertyType,
|
|
119
|
+
"number" | "date" | "created_time" | "last_edited_time"
|
|
120
|
+
>;
|
|
121
|
+
aggregation?: RollupBaseAggregationType;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type RelationProperty = PropertyBase & {
|
|
125
|
+
type: "relation";
|
|
126
|
+
targetDataSourceResourceId: ResourceId;
|
|
127
|
+
targetDataSourcePropertyResourceId?: ResourceId;
|
|
128
|
+
limit?: 1;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type FormulaProperty = PropertyBase & {
|
|
132
|
+
type: "formula";
|
|
133
|
+
expression?: string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type RollupProperty = PropertyBase &
|
|
137
|
+
RollupTarget & {
|
|
138
|
+
type: "rollup";
|
|
139
|
+
relationPropertyResourceId: ResourceId;
|
|
140
|
+
targetPropertyResourceId: ResourceId;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
type AutoIncrementIdProperty = PropertyBase & {
|
|
144
|
+
type: "auto_increment_id";
|
|
145
|
+
prefix?: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
type PersonProperty = PropertyBase & {
|
|
149
|
+
type: "person";
|
|
150
|
+
limit?: 1;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
type VerificationProperty = PropertyBase & {
|
|
154
|
+
type: "verification";
|
|
155
|
+
verifierPropertyResourceId: ResourceId;
|
|
66
156
|
};
|
|
67
157
|
|
|
68
158
|
/** A normalized Notion-as-Code property with its resolved display name. */
|
|
@@ -90,22 +180,29 @@ export type NotionAsCodeProperty =
|
|
|
90
180
|
| (PropertyBase & { type: "email" })
|
|
91
181
|
| (PropertyBase & { type: "phone_number" })
|
|
92
182
|
| (PropertyBase & { type: "file" })
|
|
93
|
-
|
|
|
183
|
+
| RelationProperty
|
|
184
|
+
| FormulaProperty
|
|
185
|
+
| RollupProperty
|
|
186
|
+
| (PropertyBase & { type: "created_time" })
|
|
187
|
+
| (PropertyBase & { type: "last_edited_time" })
|
|
188
|
+
| (PropertyBase & { type: "created_by" })
|
|
189
|
+
| (PropertyBase & { type: "last_edited_by" })
|
|
190
|
+
| AutoIncrementIdProperty
|
|
191
|
+
| PersonProperty
|
|
192
|
+
| VerificationProperty;
|
|
94
193
|
|
|
95
194
|
/**
|
|
96
195
|
* `T extends unknown` applies Omit to each property variant separately, preserving
|
|
97
196
|
* variant-specific fields such as required status options. Plain Omit on the union
|
|
98
|
-
* would retain only shared keys.
|
|
99
|
-
* unsupported variant, whose string index signature would otherwise erase them.
|
|
197
|
+
* would retain only shared keys.
|
|
100
198
|
*/
|
|
101
199
|
type DistributivePropertyConfig<T extends NotionAsCodeProperty> = T extends unknown
|
|
102
|
-
? Omit<T, "name">
|
|
200
|
+
? Omit<T, "name">
|
|
103
201
|
: never;
|
|
104
202
|
|
|
105
203
|
/**
|
|
106
204
|
* Authoring properties are keyed by their schema object name. A property-level
|
|
107
|
-
* `name` override would make the key and serialized property diverge
|
|
108
|
-
* explicitly rejected even for the string-indexed unsupported-property variant.
|
|
205
|
+
* `name` override would make the key and serialized property diverge.
|
|
109
206
|
*/
|
|
110
207
|
export type NotionAsCodePropertyConfig = DistributivePropertyConfig<NotionAsCodeProperty> & {
|
|
111
208
|
name?: never;
|
|
@@ -113,6 +210,106 @@ export type NotionAsCodePropertyConfig = DistributivePropertyConfig<NotionAsCode
|
|
|
113
210
|
/** A keyed property schema, with object keys supplying serialized property names. */
|
|
114
211
|
export type NotionAsCodeSchema = Record<string, NotionAsCodePropertyConfig>;
|
|
115
212
|
|
|
213
|
+
type SchemaProperty<Schema extends NotionAsCodeSchema> = Schema[keyof Schema & string];
|
|
214
|
+
type PropertyType = NotionAsCodePropertyConfig["type"];
|
|
215
|
+
type PropertyResourceIdFor<
|
|
216
|
+
Schema extends NotionAsCodeSchema,
|
|
217
|
+
Types extends PropertyType = PropertyType,
|
|
218
|
+
> = Extract<SchemaProperty<Schema>, { type: Types }>["resourceId"];
|
|
219
|
+
|
|
220
|
+
export type PageLayoutPropertyConfig<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
221
|
+
property: PropertyResourceIdFor<Schema>;
|
|
222
|
+
visibility?: "show" | "hide_if_empty" | "hide";
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export type PageLayoutPropertySection<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
226
|
+
name: string;
|
|
227
|
+
properties: readonly PageLayoutPropertyConfig<Schema>[];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export type PageLayoutPropertiesModule<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
231
|
+
type: "properties";
|
|
232
|
+
} & (
|
|
233
|
+
| {
|
|
234
|
+
properties?: readonly PageLayoutPropertyConfig<Schema>[];
|
|
235
|
+
sections?: never;
|
|
236
|
+
}
|
|
237
|
+
| {
|
|
238
|
+
sections: readonly PageLayoutPropertySection<Schema>[];
|
|
239
|
+
properties?: never;
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
export type PageLayoutPersonStyle = "compact" | "large";
|
|
244
|
+
export type PageLayoutNumberAndFormulaStyle = "large" | "small";
|
|
245
|
+
|
|
246
|
+
type PageLayoutStyleFor<Property extends NotionAsCodePropertyConfig> = Property["type"] extends
|
|
247
|
+
| "person"
|
|
248
|
+
| "created_by"
|
|
249
|
+
| "last_edited_by"
|
|
250
|
+
? { style?: PageLayoutPersonStyle }
|
|
251
|
+
: Property["type"] extends "number" | "formula"
|
|
252
|
+
? { style?: PageLayoutNumberAndFormulaStyle }
|
|
253
|
+
: { style?: never };
|
|
254
|
+
|
|
255
|
+
type PageLayoutPropertyModuleFor<Property extends NotionAsCodePropertyConfig> =
|
|
256
|
+
Property extends unknown
|
|
257
|
+
? {
|
|
258
|
+
type: "property";
|
|
259
|
+
property: Property["resourceId"];
|
|
260
|
+
} & PageLayoutStyleFor<Property>
|
|
261
|
+
: never;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* A standalone property module. Its available style is inferred from the
|
|
265
|
+
* referenced property's type when the layout is declared with a literal schema.
|
|
266
|
+
*/
|
|
267
|
+
export type PageLayoutPropertyModule<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
268
|
+
PageLayoutPropertyModuleFor<SchemaProperty<Schema>>;
|
|
269
|
+
|
|
270
|
+
export type PageLayoutRelationConfig<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
271
|
+
property: PropertyResourceIdFor<Schema, "relation">;
|
|
272
|
+
display: "pageSection" | "minimal";
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export type PageLayoutRelationsGroupModule<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
276
|
+
{
|
|
277
|
+
type: "relations";
|
|
278
|
+
relations: readonly PageLayoutRelationConfig<Schema>[];
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export type PageLayoutMainModule<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
282
|
+
| PageLayoutPropertiesModule<Schema>
|
|
283
|
+
| PageLayoutPropertyModule<Schema>
|
|
284
|
+
| PageLayoutRelationsGroupModule<Schema>;
|
|
285
|
+
|
|
286
|
+
export type PageLayoutSidebarModule<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
287
|
+
| PageLayoutPropertiesModule<Schema>
|
|
288
|
+
| PageLayoutPropertyModule<Schema>;
|
|
289
|
+
|
|
290
|
+
export type ContentPageLayoutTab = {
|
|
291
|
+
type: "content";
|
|
292
|
+
name?: string;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export type ViewPageLayoutTab = {
|
|
296
|
+
type: "view";
|
|
297
|
+
resourceId: ResourceId;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export type DatabasePageLayoutTab = ContentPageLayoutTab | ViewPageLayoutTab;
|
|
301
|
+
|
|
302
|
+
export type DatabasePageLayout<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
303
|
+
main: readonly PageLayoutMainModule<Schema>[];
|
|
304
|
+
sidebar?: readonly PageLayoutSidebarModule<Schema>[];
|
|
305
|
+
pinnedProperties?: readonly PropertyResourceIdFor<Schema>[];
|
|
306
|
+
discussions?: "top" | "bottom" | "hidden";
|
|
307
|
+
tabs?: readonly DatabasePageLayoutTab[];
|
|
308
|
+
fullWidth?: boolean;
|
|
309
|
+
showPropertyIcons?: boolean;
|
|
310
|
+
showTemplates?: boolean;
|
|
311
|
+
};
|
|
312
|
+
|
|
116
313
|
/** The normalized data-source shape emitted in provisioning artifacts. */
|
|
117
314
|
export type DataSourceDefinition<
|
|
118
315
|
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
@@ -121,8 +318,7 @@ export type DataSourceDefinition<
|
|
|
121
318
|
name: string;
|
|
122
319
|
properties: Properties;
|
|
123
320
|
icon?: NotionAsCodeIcon;
|
|
124
|
-
|
|
125
|
-
pageLayout?: unknown;
|
|
321
|
+
pageLayout?: DatabasePageLayout;
|
|
126
322
|
};
|
|
127
323
|
|
|
128
324
|
export type TeamspaceIntent = TeamspaceArgs & {
|
|
@@ -141,8 +337,7 @@ export type DatabaseIntent = {
|
|
|
141
337
|
dataSources: readonly DataSourceDefinition[];
|
|
142
338
|
views?: readonly ViewSchema[];
|
|
143
339
|
icon?: NotionAsCodeIcon;
|
|
144
|
-
|
|
145
|
-
cover?: unknown;
|
|
340
|
+
cover?: PageCoverReference;
|
|
146
341
|
hideDataSourceTitle?: boolean;
|
|
147
342
|
hideDatabaseTitleIfEmpty?: boolean;
|
|
148
343
|
name?: string;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { expectTypeOf, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CustomAgentArgs,
|
|
5
|
+
CustomAgentPropertyUpdatedTrigger,
|
|
6
|
+
CustomAgentTrigger,
|
|
7
|
+
DatabasePageLayout,
|
|
8
|
+
NotionAsCodePropertyConfig,
|
|
9
|
+
PageArgs,
|
|
10
|
+
PageCoverReference,
|
|
11
|
+
PageLayoutPropertyModule,
|
|
12
|
+
SingleSourceDatabaseArgs,
|
|
13
|
+
} from "./index.js";
|
|
14
|
+
|
|
15
|
+
it("types page and database covers as URL references", () => {
|
|
16
|
+
expectTypeOf<{
|
|
17
|
+
type: "url";
|
|
18
|
+
url: "https://example.com/cover.png";
|
|
19
|
+
position: 0.25;
|
|
20
|
+
}>().toExtend<PageCoverReference>();
|
|
21
|
+
expectTypeOf<{
|
|
22
|
+
type: "external";
|
|
23
|
+
url: "https://example.com/cover.png";
|
|
24
|
+
}>().not.toExtend<PageCoverReference>();
|
|
25
|
+
expectTypeOf<PageArgs["cover"]>().toEqualTypeOf<PageCoverReference | undefined>();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("requires configuration for each advanced property kind", () => {
|
|
29
|
+
expectTypeOf<{
|
|
30
|
+
type: "relation";
|
|
31
|
+
resourceId: "project";
|
|
32
|
+
targetDataSourceResourceId: "projects";
|
|
33
|
+
limit: 1;
|
|
34
|
+
}>().toExtend<NotionAsCodePropertyConfig>();
|
|
35
|
+
expectTypeOf<{
|
|
36
|
+
type: "relation";
|
|
37
|
+
resourceId: "project";
|
|
38
|
+
}>().not.toExtend<NotionAsCodePropertyConfig>();
|
|
39
|
+
|
|
40
|
+
expectTypeOf<{
|
|
41
|
+
type: "rollup";
|
|
42
|
+
resourceId: "total";
|
|
43
|
+
relationPropertyResourceId: "project";
|
|
44
|
+
targetPropertyResourceId: "estimate";
|
|
45
|
+
targetPropertyType: "number";
|
|
46
|
+
aggregation: "sum";
|
|
47
|
+
}>().toExtend<NotionAsCodePropertyConfig>();
|
|
48
|
+
expectTypeOf<{
|
|
49
|
+
type: "rollup";
|
|
50
|
+
resourceId: "invalid-total";
|
|
51
|
+
relationPropertyResourceId: "project";
|
|
52
|
+
targetPropertyResourceId: "name";
|
|
53
|
+
targetPropertyType: "title";
|
|
54
|
+
aggregation: "sum";
|
|
55
|
+
}>().not.toExtend<NotionAsCodePropertyConfig>();
|
|
56
|
+
|
|
57
|
+
expectTypeOf<{
|
|
58
|
+
type: "verification";
|
|
59
|
+
resourceId: "verification";
|
|
60
|
+
verifierPropertyResourceId: "owner";
|
|
61
|
+
}>().toExtend<NotionAsCodePropertyConfig>();
|
|
62
|
+
expectTypeOf<{
|
|
63
|
+
type: "verification";
|
|
64
|
+
resourceId: "verification";
|
|
65
|
+
}>().not.toExtend<NotionAsCodePropertyConfig>();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("links page-layout property styles and relation groups to the data source schema", () => {
|
|
69
|
+
type Schema = {
|
|
70
|
+
Name: { type: "title"; resourceId: "name" };
|
|
71
|
+
Estimate: { type: "number"; resourceId: "estimate" };
|
|
72
|
+
Owner: { type: "person"; resourceId: "owner" };
|
|
73
|
+
Project: {
|
|
74
|
+
type: "relation";
|
|
75
|
+
resourceId: "project";
|
|
76
|
+
targetDataSourceResourceId: "projects";
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
expectTypeOf<{
|
|
81
|
+
type: "property";
|
|
82
|
+
property: "estimate";
|
|
83
|
+
style: "large";
|
|
84
|
+
}>().toExtend<PageLayoutPropertyModule<Schema>>();
|
|
85
|
+
expectTypeOf<{
|
|
86
|
+
type: "property";
|
|
87
|
+
property: "estimate";
|
|
88
|
+
style: "landscape";
|
|
89
|
+
}>().not.toExtend<PageLayoutPropertyModule<Schema>>();
|
|
90
|
+
expectTypeOf<{
|
|
91
|
+
type: "property";
|
|
92
|
+
property: "name";
|
|
93
|
+
style: "large";
|
|
94
|
+
}>().not.toExtend<PageLayoutPropertyModule<Schema>>();
|
|
95
|
+
|
|
96
|
+
type ValidLayout = {
|
|
97
|
+
main: [
|
|
98
|
+
{ type: "properties"; properties: [{ property: "name" }] },
|
|
99
|
+
{
|
|
100
|
+
type: "relations";
|
|
101
|
+
relations: [{ property: "project"; display: "pageSection" }];
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
pinnedProperties: ["owner"];
|
|
105
|
+
};
|
|
106
|
+
type InvalidRelationLayout = {
|
|
107
|
+
main: [
|
|
108
|
+
{ type: "properties"; properties: [{ property: "name" }] },
|
|
109
|
+
{
|
|
110
|
+
type: "relations";
|
|
111
|
+
relations: [{ property: "estimate"; display: "pageSection" }];
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
};
|
|
115
|
+
expectTypeOf<ValidLayout>().toExtend<DatabasePageLayout<Schema>>();
|
|
116
|
+
expectTypeOf<InvalidRelationLayout>().not.toExtend<DatabasePageLayout<Schema>>();
|
|
117
|
+
|
|
118
|
+
expectTypeOf<{
|
|
119
|
+
dataSourceResourceId: "tasks";
|
|
120
|
+
name: "Tasks";
|
|
121
|
+
schema: Schema;
|
|
122
|
+
pageLayout: ValidLayout;
|
|
123
|
+
}>().toExtend<SingleSourceDatabaseArgs<Schema>>();
|
|
124
|
+
expectTypeOf<{
|
|
125
|
+
dataSourceResourceId: "tasks";
|
|
126
|
+
name: "Tasks";
|
|
127
|
+
schema: Schema;
|
|
128
|
+
pageLayout: InvalidRelationLayout;
|
|
129
|
+
}>().not.toExtend<SingleSourceDatabaseArgs<Schema>>();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("requires a meaningful custom agent trigger configuration", () => {
|
|
133
|
+
expectTypeOf<{
|
|
134
|
+
type: "recurrence";
|
|
135
|
+
resourceId: "weekday-trigger";
|
|
136
|
+
frequency: "week";
|
|
137
|
+
interval: 1;
|
|
138
|
+
start: "2026-09-21T09:00:00";
|
|
139
|
+
timeZone: "America/Los_Angeles";
|
|
140
|
+
weekdays: { MO: true; FR: true };
|
|
141
|
+
}>().toExtend<CustomAgentTrigger>();
|
|
142
|
+
expectTypeOf<{
|
|
143
|
+
type: "recurrence";
|
|
144
|
+
resourceId: "empty-weekdays";
|
|
145
|
+
frequency: "week";
|
|
146
|
+
interval: 1;
|
|
147
|
+
start: "2026-09-21T09:00:00";
|
|
148
|
+
timeZone: "America/Los_Angeles";
|
|
149
|
+
weekdays: {};
|
|
150
|
+
}>().not.toExtend<CustomAgentTrigger>();
|
|
151
|
+
|
|
152
|
+
expectTypeOf<{
|
|
153
|
+
type: "property_updated";
|
|
154
|
+
resourceId: "content-trigger";
|
|
155
|
+
dataSourceResourceId: "tasks";
|
|
156
|
+
triggerWhenPageContentEdited: true;
|
|
157
|
+
}>().toExtend<CustomAgentPropertyUpdatedTrigger>();
|
|
158
|
+
expectTypeOf<{
|
|
159
|
+
type: "property_updated";
|
|
160
|
+
resourceId: "empty-trigger";
|
|
161
|
+
dataSourceResourceId: "tasks";
|
|
162
|
+
}>().not.toExtend<CustomAgentPropertyUpdatedTrigger>();
|
|
163
|
+
|
|
164
|
+
expectTypeOf<CustomAgentArgs["triggers"]>().toEqualTypeOf<
|
|
165
|
+
readonly CustomAgentTrigger[] | undefined
|
|
166
|
+
>();
|
|
167
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { notion, type PageArgs, type PropertyValue } from "./index.js";
|
|
4
|
+
import { finishMetadataRecording, startMetadataRecording } from "./recorder.js";
|
|
5
|
+
|
|
6
|
+
describe("Notion-as-Code page declarations", () => {
|
|
7
|
+
it("records typed URL covers", () => {
|
|
8
|
+
const recorder = startMetadataRecording();
|
|
9
|
+
|
|
10
|
+
notion.page({
|
|
11
|
+
resourceId: "url-cover-page",
|
|
12
|
+
cover: {
|
|
13
|
+
type: "url",
|
|
14
|
+
url: "https://example.com/cover.png",
|
|
15
|
+
position: 0.25,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(finishMetadataRecording(recorder).intents).toEqual([
|
|
20
|
+
expect.objectContaining({
|
|
21
|
+
resourceId: "url-cover-page",
|
|
22
|
+
cover: {
|
|
23
|
+
type: "url",
|
|
24
|
+
url: "https://example.com/cover.png",
|
|
25
|
+
position: 0.25,
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("records a regular page title separately from Markdown content", () => {
|
|
32
|
+
const properties = {
|
|
33
|
+
title: notion.text("Workflow guide"),
|
|
34
|
+
} satisfies Record<string, PropertyValue | undefined>;
|
|
35
|
+
const recorder = startMetadataRecording();
|
|
36
|
+
|
|
37
|
+
notion.page({
|
|
38
|
+
resourceId: "workflow-guide",
|
|
39
|
+
properties,
|
|
40
|
+
content: "# Body heading",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expectTypeOf<PageArgs["properties"]>().toEqualTypeOf<
|
|
44
|
+
Record<string, PropertyValue | undefined> | undefined
|
|
45
|
+
>();
|
|
46
|
+
expect(finishMetadataRecording(recorder).intents).toEqual([
|
|
47
|
+
{
|
|
48
|
+
type: "page",
|
|
49
|
+
resourceId: "workflow-guide",
|
|
50
|
+
parent: {
|
|
51
|
+
type: "resourceId",
|
|
52
|
+
resourceId: "__notion_apps_workspace__",
|
|
53
|
+
},
|
|
54
|
+
properties: { title: [["Workflow guide"]] },
|
|
55
|
+
content: "# Body heading",
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
APPS_WORKSPACE_RESOURCE_ID,
|
|
3
3
|
type PageIntent,
|
|
4
|
+
type PageCoverReference,
|
|
4
5
|
type Parent,
|
|
5
6
|
type ResourceId,
|
|
6
7
|
} from "./intents.js";
|
|
@@ -8,14 +9,71 @@ import { createChildDatabase, type ChildDatabaseFactory } from "./database.js";
|
|
|
8
9
|
import { recordIntent } from "./recorder.js";
|
|
9
10
|
import { assertParent, assertUserResourceId } from "./resource.js";
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A single text token: plain text as `[string]`, or text with annotations as
|
|
14
|
+
* `[string, ...annotations]`.
|
|
15
|
+
*/
|
|
16
|
+
export type SimpleTextToken = [string] | [string, ...unknown[]];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The JSON-serializable text format returned by Notion-as-Code text helpers.
|
|
20
|
+
*/
|
|
21
|
+
export type TextValue = SimpleTextToken[];
|
|
22
|
+
|
|
23
|
+
export type VerificationInput =
|
|
24
|
+
| {
|
|
25
|
+
state: "verified";
|
|
26
|
+
/** Set an expiration with `notion.datetime(...)`; omit it for indefinite verification. */
|
|
27
|
+
datetime?: TextValue;
|
|
28
|
+
}
|
|
29
|
+
| { state: "unverified" };
|
|
30
|
+
|
|
31
|
+
export type VerificationPropertyValue = VerificationInput & {
|
|
32
|
+
type: "verification";
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A page property value accepted by Notion as Code.
|
|
37
|
+
*
|
|
38
|
+
* Use helpers from `@notionhq/apps/notion-as-code` to construct rich values
|
|
39
|
+
* such as text, dates, checkboxes, and URLs.
|
|
40
|
+
*/
|
|
41
|
+
export type PropertyValue =
|
|
42
|
+
| string
|
|
43
|
+
| number
|
|
44
|
+
| TextValue
|
|
45
|
+
| string[]
|
|
46
|
+
| VerificationPropertyValue
|
|
47
|
+
| undefined;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Arguments for declaring a page.
|
|
51
|
+
*
|
|
52
|
+
* For a regular page, set its title through
|
|
53
|
+
* `properties.title: notion.text("My Page Title")`. For a database page, use
|
|
54
|
+
* the title property's schema name instead of `title`.
|
|
55
|
+
*/
|
|
11
56
|
export type PageArgs = {
|
|
12
57
|
resourceId: ResourceId;
|
|
13
58
|
/** Omit to create a private top-level page in the Apps workspace. */
|
|
14
59
|
parent?: Parent;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
60
|
+
cover?: PageCoverReference;
|
|
61
|
+
/**
|
|
62
|
+
* Page properties, including the page title.
|
|
63
|
+
*
|
|
64
|
+
* @example Regular page
|
|
65
|
+
* properties: { title: notion.text("My Page Title") }
|
|
66
|
+
*
|
|
67
|
+
* @example Database page
|
|
68
|
+
* properties: { Name: notion.text("Task Name"), Status: "In Progress" }
|
|
69
|
+
*/
|
|
70
|
+
properties?: Record<string, PropertyValue | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Page body in Notion-flavored Markdown.
|
|
73
|
+
*
|
|
74
|
+
* Markdown headings create child blocks. They do not set the page title;
|
|
75
|
+
* set a regular page's title with `properties.title`.
|
|
76
|
+
*/
|
|
19
77
|
content?: string;
|
|
20
78
|
fullWidth?: boolean;
|
|
21
79
|
};
|