@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
|
@@ -1,11 +1,59 @@
|
|
|
1
|
-
import { type Parent, type ResourceId } from "./intents.js";
|
|
1
|
+
import { type PageCoverReference, type Parent, type ResourceId } from "./intents.js";
|
|
2
2
|
import { type ChildDatabaseFactory } from "./database.js";
|
|
3
|
+
/**
|
|
4
|
+
* A single text token: plain text as `[string]`, or text with annotations as
|
|
5
|
+
* `[string, ...annotations]`.
|
|
6
|
+
*/
|
|
7
|
+
export type SimpleTextToken = [string] | [string, ...unknown[]];
|
|
8
|
+
/**
|
|
9
|
+
* The JSON-serializable text format returned by Notion-as-Code text helpers.
|
|
10
|
+
*/
|
|
11
|
+
export type TextValue = SimpleTextToken[];
|
|
12
|
+
export type VerificationInput = {
|
|
13
|
+
state: "verified";
|
|
14
|
+
/** Set an expiration with `notion.datetime(...)`; omit it for indefinite verification. */
|
|
15
|
+
datetime?: TextValue;
|
|
16
|
+
} | {
|
|
17
|
+
state: "unverified";
|
|
18
|
+
};
|
|
19
|
+
export type VerificationPropertyValue = VerificationInput & {
|
|
20
|
+
type: "verification";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A page property value accepted by Notion as Code.
|
|
24
|
+
*
|
|
25
|
+
* Use helpers from `@notionhq/apps/notion-as-code` to construct rich values
|
|
26
|
+
* such as text, dates, checkboxes, and URLs.
|
|
27
|
+
*/
|
|
28
|
+
export type PropertyValue = string | number | TextValue | string[] | VerificationPropertyValue | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Arguments for declaring a page.
|
|
31
|
+
*
|
|
32
|
+
* For a regular page, set its title through
|
|
33
|
+
* `properties.title: notion.text("My Page Title")`. For a database page, use
|
|
34
|
+
* the title property's schema name instead of `title`.
|
|
35
|
+
*/
|
|
3
36
|
export type PageArgs = {
|
|
4
37
|
resourceId: ResourceId;
|
|
5
38
|
/** Omit to create a private top-level page in the Apps workspace. */
|
|
6
39
|
parent?: Parent;
|
|
7
|
-
cover?:
|
|
8
|
-
|
|
40
|
+
cover?: PageCoverReference;
|
|
41
|
+
/**
|
|
42
|
+
* Page properties, including the page title.
|
|
43
|
+
*
|
|
44
|
+
* @example Regular page
|
|
45
|
+
* properties: { title: notion.text("My Page Title") }
|
|
46
|
+
*
|
|
47
|
+
* @example Database page
|
|
48
|
+
* properties: { Name: notion.text("Task Name"), Status: "In Progress" }
|
|
49
|
+
*/
|
|
50
|
+
properties?: Record<string, PropertyValue | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* Page body in Notion-flavored Markdown.
|
|
53
|
+
*
|
|
54
|
+
* Markdown headings create child blocks. They do not set the page title;
|
|
55
|
+
* set a regular page's title with `properties.title`.
|
|
56
|
+
*/
|
|
9
57
|
content?: string;
|
|
10
58
|
fullWidth?: boolean;
|
|
11
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,MAAM,EACX,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAI/E,MAAM,MAAM,QAAQ,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACX,KAAK,UAAU,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAI/E;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAC1B;IACA,KAAK,EAAE,UAAU,CAAC;IAClB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,SAAS,CAAC;CACpB,GACD;IAAE,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3B,MAAM,MAAM,yBAAyB,GAAG,iBAAiB,GAAG;IAC3D,IAAI,EAAE,cAAc,CAAC;CACrB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACtB,MAAM,GACN,MAAM,GACN,SAAS,GACT,MAAM,EAAE,GACR,yBAAyB,GACzB,SAAS,CAAC;AAEb;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,CAAC;IACvD;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,oBAAoB,CAAC;IAClC,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;CAC7C,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAS/C;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAgBrF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.test.d.ts","sourceRoot":"","sources":["../../src/notion-as-code/page.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -75,7 +75,8 @@ root exports support:
|
|
|
75
75
|
|
|
76
76
|
- `teamspace({ resourceId, name, accessLevel })`, with `addPage` and
|
|
77
77
|
`teamspace.addDatabase(id, args)` on the returned handle.
|
|
78
|
-
- `page({ resourceId, parent?, properties?, content? })
|
|
78
|
+
- `page({ resourceId, parent?, properties?, content? })`; regular page titles use
|
|
79
|
+
`properties: { title: notion.text("My Page Title") }`. It also has
|
|
79
80
|
`addPage` and `page.addDatabase(id, args)` for children.
|
|
80
81
|
- `database(resourceId, { dataSourceResourceId, name, schema, parent?, views? })`,
|
|
81
82
|
returning a single source as `handle.dataSource`, or
|
|
@@ -90,6 +91,11 @@ root exports support:
|
|
|
90
91
|
Use property resource IDs in view filters, sorts, and layout options; calendar
|
|
91
92
|
views require `calendarBy`, and timeline views require `timelineBy`.
|
|
92
93
|
|
|
94
|
+
For a regular page, set the title with
|
|
95
|
+
`properties: { title: notion.text("My Page Title") }`. A Markdown heading in
|
|
96
|
+
`content` creates a child heading block; it does not set the page title. For a
|
|
97
|
+
database page, use the title property's schema name instead of `title`.
|
|
98
|
+
|
|
93
99
|
A database declaration must include at least one data source or at least one
|
|
94
100
|
view. A linked-only database may omit `datasources`, or use `datasources: {}`,
|
|
95
101
|
only when `views` is nonempty. `{}`, `{ datasources: {} }`, and
|
|
@@ -118,12 +124,17 @@ use `{ type: "resourceId", resourceId: parent.resourceId }`; child helpers set
|
|
|
118
124
|
this reference for you. Pages and databases without a parent default to private
|
|
119
125
|
top-level resources in the Apps workspace.
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
Page and database covers accept `{ type: "url", url, position? }`. Custom-agent
|
|
128
|
+
triggers are discriminated unions for recurrence, page-added, and
|
|
129
|
+
property-updated triggers. Weekly recurrences require at least one weekday, and
|
|
130
|
+
property-updated triggers require property conditions or
|
|
131
|
+
`triggerWhenPageContentEdited: true`.
|
|
132
|
+
|
|
133
|
+
Database page layouts are typed against their keyed data-source schema. Reference
|
|
134
|
+
property resource IDs, not schema keys. The SDK restricts relation groups to
|
|
135
|
+
relation properties and narrows standalone property styles by property type.
|
|
136
|
+
Database views are typed with `ViewSchema`; standalone declarations accept
|
|
137
|
+
`ViewArgs`, which adds the owning `databaseResourceId`.
|
|
127
138
|
|
|
128
139
|
The Apps SDK exposes a subset of Notion as Code. Data sources are nested inside
|
|
129
140
|
`database(resourceId, { datasources: { ... } })`, not declared by a separate
|
|
@@ -131,8 +142,7 @@ The Apps SDK exposes a subset of Notion as Code. Data sources are nested inside
|
|
|
131
142
|
Apps deployment rejects workspace creation or changes and supplies the App's
|
|
132
143
|
workspace binding itself. Value helpers and types stay on their existing
|
|
133
144
|
subpaths. For example, import `{ notion }` from `@notionhq/apps/notion-as-code`
|
|
134
|
-
for `notion.text(...)
|
|
135
|
-
reference, not an upload or file declaration. These helpers are not root exports.
|
|
145
|
+
for `notion.text(...)`. These helpers are not root exports.
|
|
136
146
|
CLI acceptance of a serialized intent envelope alone does not establish server
|
|
137
147
|
support for its contents.
|
|
138
148
|
|
package/skills/workflow/SKILL.md
CHANGED
|
@@ -120,10 +120,12 @@ guide page in `src/workflows/sayHello/lib/notion.ts`:
|
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
122
|
import { page } from "@notionhq/apps";
|
|
123
|
+
import { notion } from "@notionhq/apps/notion-as-code";
|
|
123
124
|
|
|
124
125
|
export const guide = page({
|
|
125
126
|
resourceId: "workflow-guide",
|
|
126
|
-
|
|
127
|
+
properties: { title: notion.text("Workflow guide") },
|
|
128
|
+
content: "This App runs a scheduled workflow.",
|
|
127
129
|
});
|
|
128
130
|
```
|
|
129
131
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { notion } from "./index.js";
|
|
4
|
+
import { finishMetadataRecording, startMetadataRecording } from "./recorder.js";
|
|
5
|
+
|
|
6
|
+
describe("Notion-as-Code custom agent declarations", () => {
|
|
7
|
+
it("records typed recurrence and database triggers", () => {
|
|
8
|
+
const recorder = startMetadataRecording();
|
|
9
|
+
|
|
10
|
+
notion.customAgent({
|
|
11
|
+
resourceId: "triage-agent",
|
|
12
|
+
name: "Triage agent",
|
|
13
|
+
triggers: [
|
|
14
|
+
{
|
|
15
|
+
type: "recurrence",
|
|
16
|
+
resourceId: "weekday-trigger",
|
|
17
|
+
frequency: "week",
|
|
18
|
+
interval: 1,
|
|
19
|
+
start: "2026-09-21T09:00:00",
|
|
20
|
+
timeZone: "America/Los_Angeles",
|
|
21
|
+
weekdays: { MO: true, FR: true },
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "property_updated",
|
|
25
|
+
resourceId: "task-update-trigger",
|
|
26
|
+
dataSourceResourceId: "tasks",
|
|
27
|
+
propertyConditions: {
|
|
28
|
+
status: { type: "property_edited" },
|
|
29
|
+
},
|
|
30
|
+
triggerWhenPageContentEdited: true,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(finishMetadataRecording(recorder).intents).toEqual([
|
|
36
|
+
{
|
|
37
|
+
type: "custom_agent",
|
|
38
|
+
resourceId: "triage-agent",
|
|
39
|
+
name: "Triage agent",
|
|
40
|
+
triggers: [
|
|
41
|
+
{
|
|
42
|
+
type: "recurrence",
|
|
43
|
+
resourceId: "weekday-trigger",
|
|
44
|
+
frequency: "week",
|
|
45
|
+
interval: 1,
|
|
46
|
+
start: "2026-09-21T09:00:00",
|
|
47
|
+
timeZone: "America/Los_Angeles",
|
|
48
|
+
weekdays: { MO: true, FR: true },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "property_updated",
|
|
52
|
+
resourceId: "task-update-trigger",
|
|
53
|
+
dataSourceResourceId: "tasks",
|
|
54
|
+
propertyConditions: {
|
|
55
|
+
status: { type: "property_edited" },
|
|
56
|
+
},
|
|
57
|
+
triggerWhenPageContentEdited: true,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("rejects reserved resource IDs inside triggers", () => {
|
|
65
|
+
expect(() =>
|
|
66
|
+
notion.customAgent({
|
|
67
|
+
resourceId: "triage-agent",
|
|
68
|
+
name: "Triage agent",
|
|
69
|
+
triggers: [
|
|
70
|
+
{
|
|
71
|
+
type: "page_added",
|
|
72
|
+
resourceId: "__notion_apps_trigger",
|
|
73
|
+
dataSourceResourceId: "tasks",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
}),
|
|
77
|
+
).toThrow("reserved");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -2,6 +2,105 @@ import type { CustomAgentIntent, NotionAsCodeIcon, ResourceId } from "./intents.
|
|
|
2
2
|
import { recordIntent } from "./recorder.js";
|
|
3
3
|
import { assertUserResourceId } from "./resource.js";
|
|
4
4
|
|
|
5
|
+
type RecurrenceWeekday = "MO" | "TU" | "WE" | "TH" | "FR" | "SA" | "SU";
|
|
6
|
+
|
|
7
|
+
/** A nonempty selection of weekdays for a weekly recurrence. */
|
|
8
|
+
export type RecurrenceWeekdays = {
|
|
9
|
+
[Day in RecurrenceWeekday]?: true;
|
|
10
|
+
} & {
|
|
11
|
+
[Day in RecurrenceWeekday]: Required<Pick<Record<RecurrenceWeekday, true>, Day>>;
|
|
12
|
+
}[RecurrenceWeekday];
|
|
13
|
+
|
|
14
|
+
export type MonthlyWeekdayOccurrence = "1st" | "2nd" | "3rd" | "4th" | "last";
|
|
15
|
+
|
|
16
|
+
export type MonthlyRecurrenceRestriction =
|
|
17
|
+
| { type: "day_of_month"; day: number }
|
|
18
|
+
| {
|
|
19
|
+
type: "weekday_of_month";
|
|
20
|
+
weekday: RecurrenceWeekday;
|
|
21
|
+
occurrence: MonthlyWeekdayOccurrence;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RecurrenceScheduleEnd =
|
|
25
|
+
| { type: "date"; endsAt: string }
|
|
26
|
+
| { type: "occurrences"; count: number };
|
|
27
|
+
|
|
28
|
+
type RecurrenceScheduleBase = {
|
|
29
|
+
interval: number;
|
|
30
|
+
start: string;
|
|
31
|
+
timeZone: string;
|
|
32
|
+
end?: RecurrenceScheduleEnd;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type HourlyRecurrenceSchedule = RecurrenceScheduleBase & {
|
|
36
|
+
frequency: "hour";
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type DailyRecurrenceSchedule = RecurrenceScheduleBase & {
|
|
40
|
+
frequency: "day";
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type WeeklyRecurrenceSchedule = RecurrenceScheduleBase & {
|
|
44
|
+
frequency: "week";
|
|
45
|
+
weekdays: RecurrenceWeekdays;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type MonthlyRecurrenceSchedule = RecurrenceScheduleBase & {
|
|
49
|
+
frequency: "month";
|
|
50
|
+
monthlyRestriction: MonthlyRecurrenceRestriction;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type YearlyRecurrenceSchedule = RecurrenceScheduleBase & {
|
|
54
|
+
frequency: "year";
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type CustomAgentRecurrenceSchedule = (
|
|
58
|
+
| HourlyRecurrenceSchedule
|
|
59
|
+
| DailyRecurrenceSchedule
|
|
60
|
+
| WeeklyRecurrenceSchedule
|
|
61
|
+
| MonthlyRecurrenceSchedule
|
|
62
|
+
| YearlyRecurrenceSchedule
|
|
63
|
+
) & {
|
|
64
|
+
type: "recurrence";
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type CustomAgentTriggerBase = {
|
|
68
|
+
resourceId: ResourceId;
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type CustomAgentRecurrenceTrigger = CustomAgentTriggerBase & CustomAgentRecurrenceSchedule;
|
|
73
|
+
|
|
74
|
+
export type PropertyTriggerCondition = {
|
|
75
|
+
type: "property_edited";
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type CustomAgentPageAddedTrigger = CustomAgentTriggerBase & {
|
|
79
|
+
type: "page_added";
|
|
80
|
+
dataSourceResourceId: ResourceId;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type PropertyUpdatedTriggerTarget =
|
|
84
|
+
| {
|
|
85
|
+
propertyConditions: Readonly<Record<ResourceId, PropertyTriggerCondition>>;
|
|
86
|
+
triggerWhenPageContentEdited?: boolean;
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
propertyConditions?: never;
|
|
90
|
+
triggerWhenPageContentEdited: true;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type CustomAgentPropertyUpdatedTrigger = CustomAgentTriggerBase &
|
|
94
|
+
PropertyUpdatedTriggerTarget & {
|
|
95
|
+
type: "property_updated";
|
|
96
|
+
dataSourceResourceId: ResourceId;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type CustomAgentTrigger =
|
|
100
|
+
| CustomAgentRecurrenceTrigger
|
|
101
|
+
| CustomAgentPageAddedTrigger
|
|
102
|
+
| CustomAgentPropertyUpdatedTrigger;
|
|
103
|
+
|
|
5
104
|
export type CustomAgentArgs = {
|
|
6
105
|
resourceId: ResourceId;
|
|
7
106
|
name: string;
|
|
@@ -9,8 +108,7 @@ export type CustomAgentArgs = {
|
|
|
9
108
|
instructions?: string;
|
|
10
109
|
model?: "ambrosia-tart-high" | "opal-quince-medium" | "almond-croissant-low";
|
|
11
110
|
sharedResources?: readonly ResourceId[];
|
|
12
|
-
|
|
13
|
-
triggers?: readonly unknown[];
|
|
111
|
+
triggers?: readonly CustomAgentTrigger[];
|
|
14
112
|
};
|
|
15
113
|
|
|
16
114
|
export type CustomAgentHandle = {
|
|
@@ -24,6 +122,17 @@ export function customAgent(args: CustomAgentArgs): CustomAgentHandle {
|
|
|
24
122
|
for (const resourceId of args.sharedResources ?? []) {
|
|
25
123
|
assertUserResourceId(resourceId);
|
|
26
124
|
}
|
|
125
|
+
for (const trigger of args.triggers ?? []) {
|
|
126
|
+
assertUserResourceId(trigger.resourceId);
|
|
127
|
+
if (trigger.type === "page_added" || trigger.type === "property_updated") {
|
|
128
|
+
assertUserResourceId(trigger.dataSourceResourceId);
|
|
129
|
+
}
|
|
130
|
+
if (trigger.type === "property_updated") {
|
|
131
|
+
for (const propertyResourceId of Object.keys(trigger.propertyConditions ?? {})) {
|
|
132
|
+
assertUserResourceId(propertyResourceId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
27
136
|
recordIntent({ type: "custom_agent", ...args } satisfies {
|
|
28
137
|
type: "custom_agent";
|
|
29
138
|
} & CustomAgentIntent);
|
|
@@ -8,6 +8,44 @@ import { APPS_WORKSPACE_RESOURCE_ID } from "./intents.js";
|
|
|
8
8
|
import { finishMetadataRecording, startMetadataRecording } from "./recorder.js";
|
|
9
9
|
|
|
10
10
|
describe("Notion-as-Code database declarations", () => {
|
|
11
|
+
it("records a single-source page layout on the data source", () => {
|
|
12
|
+
const recorder = startMetadataRecording();
|
|
13
|
+
|
|
14
|
+
notion.database("layout-database", {
|
|
15
|
+
dataSourceResourceId: "layout-source",
|
|
16
|
+
name: "Tasks",
|
|
17
|
+
schema: {
|
|
18
|
+
Name: { type: "title", resourceId: "name" },
|
|
19
|
+
Estimate: { type: "number", resourceId: "estimate" },
|
|
20
|
+
},
|
|
21
|
+
pageLayout: {
|
|
22
|
+
main: [
|
|
23
|
+
{ type: "properties", properties: [{ property: "name" }] },
|
|
24
|
+
{ type: "property", property: "estimate", style: "large" },
|
|
25
|
+
],
|
|
26
|
+
pinnedProperties: ["estimate"],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(finishMetadataRecording(recorder).intents).toEqual([
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
resourceId: "layout-database",
|
|
33
|
+
dataSources: [
|
|
34
|
+
expect.objectContaining({
|
|
35
|
+
resourceId: "layout-source",
|
|
36
|
+
pageLayout: {
|
|
37
|
+
main: [
|
|
38
|
+
{ type: "properties", properties: [{ property: "name" }] },
|
|
39
|
+
{ type: "property", property: "estimate", style: "large" },
|
|
40
|
+
],
|
|
41
|
+
pinnedProperties: ["estimate"],
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
11
49
|
it("converts the single-source form with keyed schemas and resolved property names", () => {
|
|
12
50
|
const recorder = startMetadataRecording();
|
|
13
51
|
const schema = {
|
|
@@ -2,6 +2,7 @@ import type { Database } from "../database.js";
|
|
|
2
2
|
import {
|
|
3
3
|
APPS_WORKSPACE_RESOURCE_ID,
|
|
4
4
|
type DatabaseIntent,
|
|
5
|
+
type DatabasePageLayout,
|
|
5
6
|
type DataSourceDefinition,
|
|
6
7
|
type NotionAsCodeIcon,
|
|
7
8
|
type NotionAsCodeProperty,
|
|
@@ -26,6 +27,7 @@ export type DataSourceArgs<Schema extends NotionAsCodeSchema = NotionAsCodeSchem
|
|
|
26
27
|
name?: never;
|
|
27
28
|
schema: Schema;
|
|
28
29
|
icon?: NotionAsCodeIcon;
|
|
30
|
+
pageLayout?: DatabasePageLayout<Schema>;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
/** Data sources keyed by the names used to access their handles. */
|
|
@@ -42,6 +44,7 @@ type SingleSourceDatabaseOptions<Schema extends NotionAsCodeSchema = NotionAsCod
|
|
|
42
44
|
dataSourceResourceId: ResourceId;
|
|
43
45
|
name: string;
|
|
44
46
|
schema: Schema;
|
|
47
|
+
pageLayout?: DatabasePageLayout<Schema>;
|
|
45
48
|
datasources?: never;
|
|
46
49
|
};
|
|
47
50
|
|
|
@@ -61,6 +64,7 @@ type MultiSourceDatabaseOptions<DataSources extends DataSourceMap = DataSourceMa
|
|
|
61
64
|
datasources: DataSources;
|
|
62
65
|
dataSourceResourceId?: never;
|
|
63
66
|
schema?: never;
|
|
67
|
+
pageLayout?: never;
|
|
64
68
|
} & (keyof NoInfer<DataSources> extends never
|
|
65
69
|
? { views: NonEmptyViewSchemas }
|
|
66
70
|
: { views?: readonly ViewSchema[] });
|
|
@@ -76,6 +80,7 @@ type ViewOnlyDatabaseOptions = DatabaseMetadata & {
|
|
|
76
80
|
datasources?: never;
|
|
77
81
|
dataSourceResourceId?: never;
|
|
78
82
|
schema?: never;
|
|
83
|
+
pageLayout?: never;
|
|
79
84
|
views: NonEmptyViewSchemas;
|
|
80
85
|
};
|
|
81
86
|
|
|
@@ -255,6 +260,7 @@ function buildDatabaseHandle(
|
|
|
255
260
|
resourceId: args.dataSourceResourceId,
|
|
256
261
|
name: args.name,
|
|
257
262
|
properties: normalizeProperties(args.schema),
|
|
263
|
+
...(args.pageLayout === undefined ? {} : { pageLayout: args.pageLayout }),
|
|
258
264
|
};
|
|
259
265
|
const handle = recordDatabase(databaseResourceId, args, parent, [
|
|
260
266
|
{ key: "dataSource", schema: args.schema, definition: source },
|
|
@@ -295,6 +301,7 @@ function normalizeDatabaseMetadata(args: DatabaseArgs): DatabaseMetadata {
|
|
|
295
301
|
schema: _schema,
|
|
296
302
|
datasources: _datasources,
|
|
297
303
|
dataSourceResourceId: _dataSourceResourceId,
|
|
304
|
+
pageLayout: _pageLayout,
|
|
298
305
|
...metadata
|
|
299
306
|
} = args;
|
|
300
307
|
return metadata;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { TextValue } from "./page.js";
|
|
2
|
+
|
|
1
3
|
/** Serialize Notion-as-Code date values for Apps property payloads. */
|
|
2
|
-
export function date(startDate: string, endDate?: string):
|
|
4
|
+
export function date(startDate: string, endDate?: string): TextValue {
|
|
3
5
|
const start_date = parseIsoDate(startDate);
|
|
4
6
|
const end_date = endDate === undefined ? undefined : parseIsoDate(endDate);
|
|
5
7
|
if (end_date !== undefined && end_date < start_date) {
|
|
@@ -13,7 +15,7 @@ export function date(startDate: string, endDate?: string): unknown[][] {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/** Serialize Notion-as-Code datetime values for Apps property payloads. */
|
|
16
|
-
export function datetime(value: { start: string; end?: string; timeZone?: string }):
|
|
18
|
+
export function datetime(value: { start: string; end?: string; timeZone?: string }): TextValue {
|
|
17
19
|
const timeZone = value.timeZone ?? "UTC";
|
|
18
20
|
try {
|
|
19
21
|
new Intl.DateTimeFormat("en-US", { timeZone }).format();
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { customAgent } from "./custom-agent.js";
|
|
2
2
|
import { database } from "./database.js";
|
|
3
3
|
import { date, datetime } from "./date.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
page,
|
|
6
|
+
type TextValue,
|
|
7
|
+
type VerificationInput,
|
|
8
|
+
type VerificationPropertyValue,
|
|
9
|
+
} from "./page.js";
|
|
6
10
|
import { teamspace } from "./teamspace.js";
|
|
7
11
|
import { view } from "./view.js";
|
|
8
12
|
|
|
@@ -12,22 +16,22 @@ const notionAsCodeFacade = {
|
|
|
12
16
|
page,
|
|
13
17
|
customAgent,
|
|
14
18
|
view,
|
|
15
|
-
text: (value: string) => [[value]],
|
|
16
|
-
number: (value: number) => [[String(value)]],
|
|
19
|
+
text: (value: string): TextValue => [[value]],
|
|
20
|
+
number: (value: number): TextValue => [[String(value)]],
|
|
17
21
|
date,
|
|
18
22
|
datetime,
|
|
23
|
+
verification: (value: VerificationInput): VerificationPropertyValue => ({
|
|
24
|
+
...value,
|
|
25
|
+
type: "verification",
|
|
26
|
+
}),
|
|
19
27
|
select: (value: string) => value,
|
|
20
28
|
status: (value: string) => value,
|
|
21
29
|
multiSelect: (values: readonly string[]) => values.join(","),
|
|
22
|
-
checkbox: (value: boolean) => [[value ? "Yes" : "No"]],
|
|
23
|
-
url: (value: string) => [[value, [["a", value]]]],
|
|
24
|
-
email: (value: string) => [[value, [["a", `mailto:${value}`]]]],
|
|
25
|
-
phone: (value: string) => [[value]],
|
|
30
|
+
checkbox: (value: boolean): TextValue => [[value ? "Yes" : "No"]],
|
|
31
|
+
url: (value: string): TextValue => [[value, [["a", value]]]],
|
|
32
|
+
email: (value: string): TextValue => [[value, [["a", `mailto:${value}`]]]],
|
|
33
|
+
phone: (value: string): TextValue => [[value]],
|
|
26
34
|
relation: (value: string | readonly string[]) => (Array.isArray(value) ? [...value] : value),
|
|
27
|
-
file: (resourceId: string) => {
|
|
28
|
-
assertUserResourceId(resourceId);
|
|
29
|
-
return { type: "file" as const, resourceId };
|
|
30
|
-
},
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
export type NotionAsCodeFacade = typeof notionAsCodeFacade;
|
|
@@ -2,6 +2,9 @@ import { createNotion } from "./handles.js";
|
|
|
2
2
|
|
|
3
3
|
export { APPS_RESERVED_RESOURCE_ID_PREFIX, APPS_WORKSPACE_RESOURCE_ID } from "./intents.js";
|
|
4
4
|
export type {
|
|
5
|
+
ContentPageLayoutTab,
|
|
6
|
+
DatabasePageLayout,
|
|
7
|
+
DatabasePageLayoutTab,
|
|
5
8
|
DatabaseIntent,
|
|
6
9
|
DataSourceDefinition,
|
|
7
10
|
NotionAsCodeIcon,
|
|
@@ -10,10 +13,45 @@ export type {
|
|
|
10
13
|
NotionAsCodeSchema,
|
|
11
14
|
NotionAsCodeSelectOption,
|
|
12
15
|
NotionAsCodeStatusOption,
|
|
16
|
+
PageCoverReference,
|
|
17
|
+
PageLayoutMainModule,
|
|
18
|
+
PageLayoutNumberAndFormulaStyle,
|
|
19
|
+
PageLayoutPersonStyle,
|
|
20
|
+
PageLayoutPropertiesModule,
|
|
21
|
+
PageLayoutPropertyConfig,
|
|
22
|
+
PageLayoutPropertyModule,
|
|
23
|
+
PageLayoutPropertySection,
|
|
24
|
+
PageLayoutRelationConfig,
|
|
25
|
+
PageLayoutRelationsGroupModule,
|
|
26
|
+
PageLayoutSidebarModule,
|
|
13
27
|
Parent,
|
|
14
28
|
ResourceId,
|
|
29
|
+
RollupBaseAggregationType,
|
|
30
|
+
RollupDateAggregationType,
|
|
31
|
+
RollupNumberAggregationType,
|
|
32
|
+
RollupTargetPropertyType,
|
|
33
|
+
ViewPageLayoutTab,
|
|
15
34
|
} from "./intents.js";
|
|
16
|
-
export type {
|
|
35
|
+
export type {
|
|
36
|
+
CustomAgentArgs,
|
|
37
|
+
CustomAgentHandle,
|
|
38
|
+
CustomAgentPageAddedTrigger,
|
|
39
|
+
CustomAgentPropertyUpdatedTrigger,
|
|
40
|
+
CustomAgentRecurrenceSchedule,
|
|
41
|
+
CustomAgentRecurrenceTrigger,
|
|
42
|
+
CustomAgentTrigger,
|
|
43
|
+
CustomAgentTriggerBase,
|
|
44
|
+
DailyRecurrenceSchedule,
|
|
45
|
+
HourlyRecurrenceSchedule,
|
|
46
|
+
MonthlyRecurrenceRestriction,
|
|
47
|
+
MonthlyRecurrenceSchedule,
|
|
48
|
+
MonthlyWeekdayOccurrence,
|
|
49
|
+
PropertyTriggerCondition,
|
|
50
|
+
RecurrenceScheduleEnd,
|
|
51
|
+
RecurrenceWeekdays,
|
|
52
|
+
WeeklyRecurrenceSchedule,
|
|
53
|
+
YearlyRecurrenceSchedule,
|
|
54
|
+
} from "./custom-agent.js";
|
|
17
55
|
export type {
|
|
18
56
|
ChildDatabaseArgs,
|
|
19
57
|
ChildDatabaseFactory,
|
|
@@ -28,7 +66,16 @@ export type {
|
|
|
28
66
|
SingleSourceDatabaseArgs,
|
|
29
67
|
SingleSourceDatabaseHandle,
|
|
30
68
|
} from "./database.js";
|
|
31
|
-
export type {
|
|
69
|
+
export type {
|
|
70
|
+
ChildPageArgs,
|
|
71
|
+
PageArgs,
|
|
72
|
+
PageHandle,
|
|
73
|
+
PropertyValue,
|
|
74
|
+
SimpleTextToken,
|
|
75
|
+
TextValue,
|
|
76
|
+
VerificationInput,
|
|
77
|
+
VerificationPropertyValue,
|
|
78
|
+
} from "./page.js";
|
|
32
79
|
export type { TeamspaceArgs, TeamspaceHandle } from "./teamspace.js";
|
|
33
80
|
export type { ViewArgs, ViewHandle } from "./view.js";
|
|
34
81
|
export type {
|