@notionhq/apps 0.0.15 → 0.0.17
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/AGENTS.md +13 -0
- package/README.md +69 -2
- package/dist/connections.d.ts +39 -11
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +85 -21
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/notion-as-code/database.d.ts +26 -3
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +27 -0
- package/dist/notion-as-code/handles.d.ts +2 -0
- package/dist/notion-as-code/handles.d.ts.map +1 -1
- package/dist/notion-as-code/handles.js +2 -0
- package/dist/notion-as-code/index.d.ts +4 -1
- package/dist/notion-as-code/index.d.ts.map +1 -1
- package/dist/notion-as-code/intents.d.ts +2 -1
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/page.d.ts +3 -3
- package/dist/notion-as-code/page.d.ts.map +1 -1
- package/dist/notion-as-code/page.js +2 -7
- package/dist/notion-as-code/teamspace.d.ts +3 -3
- package/dist/notion-as-code/teamspace.d.ts.map +1 -1
- package/dist/notion-as-code/teamspace.js +2 -7
- package/dist/notion-as-code/view.d.ts +12 -0
- package/dist/notion-as-code/view.d.ts.map +1 -0
- package/dist/notion-as-code/view.js +13 -0
- package/dist/notion-as-code/views-types.test.d.ts +2 -0
- package/dist/notion-as-code/views-types.test.d.ts.map +1 -0
- package/dist/notion-as-code/views.d.ts +469 -0
- package/dist/notion-as-code/views.d.ts.map +1 -0
- package/dist/notion-as-code/views.js +0 -0
- package/dist/oauth.d.ts +11 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +26 -0
- package/dist/providers.generated.d.ts +26 -86
- package/dist/providers.generated.d.ts.map +1 -1
- package/dist/providers.generated.js +54 -66
- package/dist/triggers.generated.d.ts +3 -3
- package/dist/triggers.generated.d.ts.map +1 -1
- package/dist/workflow.d.ts +8 -8
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +12 -6
- package/docs/CONNECTIONS.md +70 -22
- package/package.json +3 -1
- package/skills/connections/SKILL.md +85 -0
- package/skills/custom-blocks/SKILL.md +45 -0
- package/skills/notion-as-code/SKILL.md +195 -0
- package/skills/sync/SKILL.md +98 -0
- package/skills/workflow/SKILL.md +123 -0
- package/src/cli/build.test.ts +95 -3
- package/src/connections.test.ts +205 -41
- package/src/connections.ts +144 -38
- package/src/index.ts +1 -0
- package/src/notion-as-code/database.ts +75 -5
- package/src/notion-as-code/handles.ts +2 -0
- package/src/notion-as-code/index.ts +59 -0
- package/src/notion-as-code/intents.ts +2 -2
- package/src/notion-as-code/page.ts +3 -11
- package/src/notion-as-code/teamspace.ts +3 -11
- package/src/notion-as-code/view.ts +23 -0
- package/src/notion-as-code/views-types.test.ts +59 -0
- package/src/notion-as-code/views.ts +554 -0
- package/src/oauth.ts +40 -0
- package/src/providers.generated.ts +68 -163
- package/src/triggers.generated.ts +4 -4
- package/src/workflow-connections-types.test.ts +16 -10
- package/src/workflow-types.test.ts +88 -16
- package/src/workflow.test.ts +45 -17
- package/src/workflow.ts +31 -17
package/src/connections.ts
CHANGED
|
@@ -1,61 +1,127 @@
|
|
|
1
|
+
import { validateOAuthConfiguration, type OAuthConfiguration } from "./oauth.js";
|
|
1
2
|
import { connectionTriggerDefinitions } from "./connection-trigger-definitions.generated.js";
|
|
2
3
|
import type { WorkflowTrigger } from "./triggers.generated.js";
|
|
3
4
|
import type { Client } from "@notionhq/client";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
connections as providerConnections,
|
|
7
|
+
createConnectionClient,
|
|
8
|
+
type ConnectionClients,
|
|
9
|
+
} from "./providers.generated.js";
|
|
5
10
|
import type { ConnectionActionInvoker } from "./connection-actions.js";
|
|
6
11
|
|
|
7
|
-
/** A setup requirement. The
|
|
8
|
-
export type WorkflowConnection<TProvider extends string = string
|
|
12
|
+
/** A setup requirement. The property name in a workflow supplies its key. */
|
|
13
|
+
export type WorkflowConnection<TProvider extends string = string> = {
|
|
9
14
|
type: TProvider;
|
|
10
|
-
|
|
15
|
+
oauth?: OAuthConfiguration;
|
|
11
16
|
};
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
export type WorkflowConnectionDeclarations = Readonly<Record<string, WorkflowConnection>>;
|
|
19
|
+
|
|
20
|
+
/** Serialized connection requirement sent in the workflow manifest. */
|
|
21
|
+
export type WorkflowConnectionRequirement = WorkflowConnection & { key: string };
|
|
22
|
+
|
|
23
|
+
/** Declared property names belonging to one provider. */
|
|
14
24
|
export type WorkflowConnectionKeys<
|
|
15
|
-
TConnections extends
|
|
25
|
+
TConnections extends WorkflowConnectionDeclarations,
|
|
16
26
|
TProvider extends string,
|
|
17
|
-
> =
|
|
18
|
-
|
|
19
|
-
type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
: never;
|
|
27
|
+
> = Extract<
|
|
28
|
+
{
|
|
29
|
+
[TKey in keyof TConnections]: TConnections[TKey]["type"] extends TProvider ? TKey : never;
|
|
30
|
+
}[keyof TConnections],
|
|
31
|
+
string
|
|
32
|
+
>;
|
|
33
|
+
|
|
25
34
|
type ConnectionBinding = { key: string; type: string; id: string };
|
|
26
35
|
|
|
27
36
|
/** Requirements are declarations, not credentials or permission grants. */
|
|
28
|
-
export
|
|
37
|
+
export const connections = {
|
|
38
|
+
...providerConnections,
|
|
39
|
+
oauth(options: OAuthConfiguration): WorkflowConnection<"oauth"> {
|
|
40
|
+
validateOAuthConfiguration(options);
|
|
41
|
+
return { type: "oauth", oauth: structuredClone(options) };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export type { OAuthConfiguration } from "./oauth.js";
|
|
45
|
+
export type OAuthConnection = { accessToken(): Promise<string> };
|
|
46
|
+
|
|
47
|
+
type WorkflowConnectionClients = ConnectionClients & { oauth: OAuthConnection };
|
|
48
|
+
|
|
49
|
+
type WorkflowConnectionClient = WorkflowConnectionClients[keyof WorkflowConnectionClients];
|
|
29
50
|
|
|
30
51
|
export type WorkflowConnections<
|
|
31
|
-
TConnections extends
|
|
32
|
-
> =
|
|
52
|
+
TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations,
|
|
53
|
+
> = {
|
|
54
|
+
readonly [TKey in keyof TConnections]: WorkflowConnectionClients[Extract<
|
|
55
|
+
keyof WorkflowConnectionClients,
|
|
56
|
+
TConnections[TKey]["type"]
|
|
57
|
+
>];
|
|
58
|
+
};
|
|
33
59
|
|
|
34
60
|
/** @internal */
|
|
35
|
-
export function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
export function normalizeConnectionRequirements(
|
|
62
|
+
declarations: WorkflowConnectionDeclarations,
|
|
63
|
+
): WorkflowConnectionRequirement[] {
|
|
64
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) {
|
|
65
|
+
throw new Error("Workflow connections must be an object keyed by connection name.");
|
|
66
|
+
}
|
|
67
|
+
const prototype: unknown = Object.getPrototypeOf(declarations);
|
|
68
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
69
|
+
throw new Error("Workflow connections must contain only own connection declarations.");
|
|
70
|
+
}
|
|
71
|
+
const keys = Object.keys(declarations);
|
|
72
|
+
if (Reflect.ownKeys(declarations).length !== keys.length) {
|
|
73
|
+
throw new Error("Workflow connection keys must be enumerable strings.");
|
|
74
|
+
}
|
|
75
|
+
if (keys.length > 100) throw new Error("Workflows support at most 100 connections.");
|
|
76
|
+
return Object.entries(declarations).map(([key, requirement]) => {
|
|
77
|
+
if (
|
|
78
|
+
!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(key) ||
|
|
79
|
+
["__proto__", "prototype", "constructor"].includes(key)
|
|
80
|
+
) {
|
|
42
81
|
throw new Error("Invalid workflow connection key: " + key);
|
|
43
82
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
83
|
+
if (
|
|
84
|
+
typeof requirement !== "object" ||
|
|
85
|
+
requirement === null ||
|
|
86
|
+
typeof requirement.type !== "string" ||
|
|
87
|
+
!requirement.type ||
|
|
88
|
+
!Object.hasOwn(requirement, "type")
|
|
89
|
+
) {
|
|
90
|
+
throw new Error("A workflow connection needs a provider type.");
|
|
91
|
+
}
|
|
92
|
+
if ("key" in requirement)
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Declare a connection key as its workflow object property, not in the provider options.",
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (requirement.type === "oauth") {
|
|
98
|
+
if (!requirement.oauth)
|
|
99
|
+
throw new Error("OAuth connections require provider configuration.");
|
|
100
|
+
validateOAuthConfiguration(requirement.oauth);
|
|
101
|
+
} else if (requirement.oauth !== undefined) {
|
|
102
|
+
throw new Error("OAuth configuration is only valid on OAuth connections.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
type: requirement.type,
|
|
107
|
+
key,
|
|
108
|
+
...(requirement.oauth === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { oauth: structuredClone(requirement.oauth) }),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
47
113
|
}
|
|
48
114
|
|
|
49
115
|
/** @internal */
|
|
50
116
|
export function validateTriggerConnections(
|
|
51
117
|
triggers: readonly WorkflowTrigger[],
|
|
52
|
-
requirements: readonly
|
|
118
|
+
requirements: readonly WorkflowConnectionRequirement[],
|
|
53
119
|
): void {
|
|
54
120
|
const seen = new Set<string>();
|
|
55
121
|
for (const trigger of triggers) {
|
|
56
122
|
if (!("connectionKey" in trigger) || trigger.connectionKey === undefined) continue;
|
|
57
123
|
const requirement = requirements.find(
|
|
58
|
-
(connection) =>
|
|
124
|
+
(connection) => connection.key === trigger.connectionKey,
|
|
59
125
|
);
|
|
60
126
|
const definition = connectionTriggerDefinitions.find(
|
|
61
127
|
(definition) => definition.type === trigger.type,
|
|
@@ -75,10 +141,41 @@ export function validateTriggerConnections(
|
|
|
75
141
|
}
|
|
76
142
|
|
|
77
143
|
/** @internal */
|
|
78
|
-
export function createWorkflowConnections
|
|
79
|
-
|
|
80
|
-
|
|
144
|
+
export function createWorkflowConnections<TConnections extends WorkflowConnectionDeclarations>(
|
|
145
|
+
notion: Client,
|
|
146
|
+
declarations: TConnections | undefined,
|
|
147
|
+
): WorkflowConnections<TConnections>;
|
|
148
|
+
export function createWorkflowConnections(
|
|
149
|
+
notion: Client,
|
|
150
|
+
declarations: WorkflowConnectionDeclarations | undefined,
|
|
151
|
+
): Record<string, WorkflowConnectionClient> {
|
|
152
|
+
const requirements = normalizeConnectionRequirements(declarations ?? {});
|
|
153
|
+
// Capture this execution's metadata, but do not parse unused bindings.
|
|
81
154
|
const rawBindings = process.env.NOTION_CONNECTION_BINDINGS;
|
|
155
|
+
const clients: Record<string, WorkflowConnectionClient> = {};
|
|
156
|
+
Object.setPrototypeOf(clients, null);
|
|
157
|
+
for (const requirement of requirements) {
|
|
158
|
+
let client: WorkflowConnectionClient | undefined;
|
|
159
|
+
Object.defineProperty(clients, requirement.key, {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
get() {
|
|
162
|
+
if (client) return client;
|
|
163
|
+
const binding = resolveBinding(requirement.key);
|
|
164
|
+
if (binding.type !== requirement.type) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Workflow connection "${requirement.key}" is ${binding.type}, not ${requirement.type}.`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
client =
|
|
170
|
+
requirement.type === "oauth"
|
|
171
|
+
? createOAuthConnection(binding)
|
|
172
|
+
: createConnectionClient(requirement.type, invoker(binding));
|
|
173
|
+
return client;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return Object.freeze(clients);
|
|
178
|
+
|
|
82
179
|
function resolveBinding(key: string): ConnectionBinding {
|
|
83
180
|
const bindings: unknown = JSON.parse(rawBindings ?? "[]");
|
|
84
181
|
if (!Array.isArray(bindings) || !bindings.every(isConnectionBinding)) {
|
|
@@ -92,11 +189,7 @@ export function createWorkflowConnections(notion: Client): WorkflowConnections {
|
|
|
92
189
|
}
|
|
93
190
|
return { ...matches[0] };
|
|
94
191
|
}
|
|
95
|
-
function invoker(
|
|
96
|
-
const binding = resolveBinding(key);
|
|
97
|
-
if (binding.type !== provider) {
|
|
98
|
-
throw new Error(`Workflow connection "${key}" is ${binding.type}, not ${provider}.`);
|
|
99
|
-
}
|
|
192
|
+
function invoker(binding: ConnectionBinding): ConnectionActionInvoker {
|
|
100
193
|
return async <Result>(action: string, args: object): Promise<Result> => {
|
|
101
194
|
const response = await notion.request<{ result: Result }>({
|
|
102
195
|
path: "tools/run/internal",
|
|
@@ -106,7 +199,20 @@ export function createWorkflowConnections(notion: Client): WorkflowConnections {
|
|
|
106
199
|
return response.result;
|
|
107
200
|
};
|
|
108
201
|
}
|
|
109
|
-
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createOAuthConnection(binding: ConnectionBinding): OAuthConnection {
|
|
205
|
+
const envKey = `NOTION_OAUTH_CONNECTION_${binding.id.replaceAll("-", "").toUpperCase()}`;
|
|
206
|
+
const token = process.env[envKey];
|
|
207
|
+
return {
|
|
208
|
+
async accessToken() {
|
|
209
|
+
if (!token)
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Workflow OAuth connection "${binding.key}" requires authorization.`,
|
|
212
|
+
);
|
|
213
|
+
return token;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
110
216
|
}
|
|
111
217
|
|
|
112
218
|
function isConnectionBinding(value: unknown): value is ConnectionBinding {
|
package/src/index.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { customAgent } from "./notion-as-code/custom-agent.js";
|
|
|
3
3
|
export { database } from "./notion-as-code/database.js";
|
|
4
4
|
export { page } from "./notion-as-code/page.js";
|
|
5
5
|
export { teamspace } from "./notion-as-code/teamspace.js";
|
|
6
|
+
export { view } from "./notion-as-code/view.js";
|
|
6
7
|
export { sync } from "./sync.js";
|
|
7
8
|
export { workflow } from "./workflow.js";
|
|
@@ -12,6 +12,7 @@ import { createPage, type ChildPageArgs, type PageHandle } from "./page.js";
|
|
|
12
12
|
import { recordIntent } from "./recorder.js";
|
|
13
13
|
import { assertParent, assertUserResourceId } from "./resource.js";
|
|
14
14
|
import { createAppsDatabase, type AppsSchemaForProperties } from "./schema.js";
|
|
15
|
+
import type { ViewSchema } from "./views.js";
|
|
15
16
|
|
|
16
17
|
/** Input for declaring a database and its optional data sources. */
|
|
17
18
|
export type DatabaseArgs<
|
|
@@ -21,8 +22,7 @@ export type DatabaseArgs<
|
|
|
21
22
|
/** Omit to create a private top-level database in the Apps workspace. */
|
|
22
23
|
parent?: Parent;
|
|
23
24
|
dataSources?: DataSources;
|
|
24
|
-
|
|
25
|
-
views?: readonly unknown[];
|
|
25
|
+
views?: readonly ViewSchema[];
|
|
26
26
|
icon?: NotionAsCodeIcon;
|
|
27
27
|
// TODO: Port cover types.
|
|
28
28
|
cover?: unknown;
|
|
@@ -37,6 +37,19 @@ export type ChildDatabaseArgs<
|
|
|
37
37
|
DataSources extends readonly DataSourceDefinition[] = readonly DataSourceDefinition[],
|
|
38
38
|
> = Omit<DatabaseArgs<DataSources>, "parent">;
|
|
39
39
|
|
|
40
|
+
/** Single-source shorthand; the source ID is `${resourceId}-source`. */
|
|
41
|
+
export type SingleSourceDatabaseArgs<
|
|
42
|
+
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
43
|
+
> = Omit<DatabaseArgs, "dataSources" | "name"> & {
|
|
44
|
+
name: string;
|
|
45
|
+
properties: Properties;
|
|
46
|
+
dataSources?: never;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type ChildSingleSourceDatabaseArgs<
|
|
50
|
+
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
51
|
+
> = Omit<SingleSourceDatabaseArgs<Properties>, "parent">;
|
|
52
|
+
|
|
40
53
|
/** Resolves one data source ID to the properties declared for it. */
|
|
41
54
|
type DataSourcePropertiesForResourceId<
|
|
42
55
|
DataSources extends readonly DataSourceDefinition[],
|
|
@@ -68,14 +81,35 @@ export type DatabaseHandle<
|
|
|
68
81
|
DataSourcePropertiesForResourceId<DataSources, ResourceId>
|
|
69
82
|
>;
|
|
70
83
|
};
|
|
71
|
-
addView: (view:
|
|
84
|
+
addView: (view: ViewSchema) => void;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** A database whose sole data source is available without an ID lookup. */
|
|
88
|
+
export type SingleSourceDatabaseHandle<
|
|
89
|
+
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
90
|
+
> = DatabaseHandle<readonly DataSourceDefinition<Properties>[]> & {
|
|
91
|
+
readonly dataSource: DataSourceHandle<Properties>;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type ChildDatabaseFactory = {
|
|
95
|
+
<const Properties extends readonly NotionAsCodeProperty[]>(
|
|
96
|
+
args: ChildSingleSourceDatabaseArgs<Properties>,
|
|
97
|
+
): SingleSourceDatabaseHandle<Properties>;
|
|
98
|
+
<const DataSources extends readonly DataSourceDefinition[]>(
|
|
99
|
+
args: ChildDatabaseArgs<DataSources> & { properties?: never },
|
|
100
|
+
): DatabaseHandle<DataSources>;
|
|
72
101
|
};
|
|
73
102
|
|
|
74
103
|
/** Declare a database, defaulting to a private top-level database in the Apps workspace. */
|
|
104
|
+
export function database<const Properties extends readonly NotionAsCodeProperty[]>(
|
|
105
|
+
args: SingleSourceDatabaseArgs<Properties>,
|
|
106
|
+
): SingleSourceDatabaseHandle<Properties>;
|
|
75
107
|
export function database<const DataSources extends readonly DataSourceDefinition[]>(
|
|
76
|
-
args: DatabaseArgs<DataSources
|
|
108
|
+
args: DatabaseArgs<DataSources> & { properties?: never },
|
|
77
109
|
): DatabaseHandle<DataSources>;
|
|
78
|
-
export function database(
|
|
110
|
+
export function database(
|
|
111
|
+
args: DatabaseArgs | SingleSourceDatabaseArgs,
|
|
112
|
+
): DatabaseHandle | SingleSourceDatabaseHandle {
|
|
79
113
|
if (args.parent) {
|
|
80
114
|
assertParent(args.parent);
|
|
81
115
|
}
|
|
@@ -83,9 +117,45 @@ export function database(args: DatabaseArgs): DatabaseHandle {
|
|
|
83
117
|
type: "resourceId",
|
|
84
118
|
resourceId: APPS_WORKSPACE_RESOURCE_ID,
|
|
85
119
|
};
|
|
120
|
+
if ("properties" in args) {
|
|
121
|
+
if (args.dataSources !== undefined) {
|
|
122
|
+
throw new Error("Specify either properties or dataSources, not both");
|
|
123
|
+
}
|
|
124
|
+
const { properties, ...databaseArgs } = args;
|
|
125
|
+
const source: DataSourceDefinition = {
|
|
126
|
+
resourceId: `${args.resourceId}-source`,
|
|
127
|
+
name: args.name,
|
|
128
|
+
properties,
|
|
129
|
+
};
|
|
130
|
+
const handle = createDatabase({ ...databaseArgs, dataSources: [source] }, parent);
|
|
131
|
+
const dataSource = handle.dataSources[source.resourceId];
|
|
132
|
+
if (!dataSource) {
|
|
133
|
+
throw new Error(`Missing data source "${source.resourceId}"`);
|
|
134
|
+
}
|
|
135
|
+
return { ...handle, dataSource };
|
|
136
|
+
}
|
|
86
137
|
return createDatabase(args, parent);
|
|
87
138
|
}
|
|
88
139
|
|
|
140
|
+
/** Bind both database declaration forms to an existing parent. */
|
|
141
|
+
export function createChildDatabase(parent: Parent): ChildDatabaseFactory {
|
|
142
|
+
function addDatabase<const Properties extends readonly NotionAsCodeProperty[]>(
|
|
143
|
+
args: ChildSingleSourceDatabaseArgs<Properties>,
|
|
144
|
+
): SingleSourceDatabaseHandle<Properties>;
|
|
145
|
+
function addDatabase<const DataSources extends readonly DataSourceDefinition[]>(
|
|
146
|
+
args: ChildDatabaseArgs<DataSources> & { properties?: never },
|
|
147
|
+
): DatabaseHandle<DataSources>;
|
|
148
|
+
function addDatabase(
|
|
149
|
+
args: ChildDatabaseArgs | ChildSingleSourceDatabaseArgs,
|
|
150
|
+
): DatabaseHandle | SingleSourceDatabaseHandle {
|
|
151
|
+
if ("properties" in args) {
|
|
152
|
+
return database({ ...args, parent });
|
|
153
|
+
}
|
|
154
|
+
return database({ ...args, parent });
|
|
155
|
+
}
|
|
156
|
+
return addDatabase;
|
|
157
|
+
}
|
|
158
|
+
|
|
89
159
|
/** Record a database declaration and create handles for each of its data sources. */
|
|
90
160
|
export function createDatabase(args: DatabaseArgs, parent: Parent): DatabaseHandle {
|
|
91
161
|
assertUserResourceId(args.resourceId);
|
|
@@ -4,12 +4,14 @@ import { date, datetime } from "./date.js";
|
|
|
4
4
|
import { page } from "./page.js";
|
|
5
5
|
import { assertUserResourceId } from "./resource.js";
|
|
6
6
|
import { teamspace } from "./teamspace.js";
|
|
7
|
+
import { view } from "./view.js";
|
|
7
8
|
|
|
8
9
|
const notionAsCodeFacade = {
|
|
9
10
|
teamspace,
|
|
10
11
|
database,
|
|
11
12
|
page,
|
|
12
13
|
customAgent,
|
|
14
|
+
view,
|
|
13
15
|
text: (value: string) => [[value]],
|
|
14
16
|
number: (value: number) => [[String(value)]],
|
|
15
17
|
date,
|
|
@@ -5,12 +5,71 @@ export type { DataSourceDefinition, NotionAsCodeProperty, Parent } from "./inten
|
|
|
5
5
|
export type { CustomAgentArgs, CustomAgentHandle } from "./custom-agent.js";
|
|
6
6
|
export type {
|
|
7
7
|
ChildDatabaseArgs,
|
|
8
|
+
ChildDatabaseFactory,
|
|
9
|
+
ChildSingleSourceDatabaseArgs,
|
|
8
10
|
DataSourceHandle,
|
|
9
11
|
DatabaseArgs,
|
|
10
12
|
DatabaseHandle,
|
|
13
|
+
SingleSourceDatabaseArgs,
|
|
14
|
+
SingleSourceDatabaseHandle,
|
|
11
15
|
} from "./database.js";
|
|
12
16
|
export type { ChildPageArgs, PageArgs, PageHandle } from "./page.js";
|
|
13
17
|
export type { TeamspaceArgs, TeamspaceHandle } from "./teamspace.js";
|
|
18
|
+
export type { ViewArgs, ViewHandle } from "./view.js";
|
|
19
|
+
export type {
|
|
20
|
+
AdvancedFilterSchema,
|
|
21
|
+
BasePropertyFilter,
|
|
22
|
+
BaseViewSchema,
|
|
23
|
+
BoardViewSchema,
|
|
24
|
+
CalendarViewSchema,
|
|
25
|
+
CheckboxGroupByFormat,
|
|
26
|
+
CheckboxPropertyFilter,
|
|
27
|
+
CoverAspectFormat,
|
|
28
|
+
CoverFormat,
|
|
29
|
+
CoverSizeFormat,
|
|
30
|
+
DatabaseViewSortDirection,
|
|
31
|
+
DateFilterMode,
|
|
32
|
+
DateGroupByFormat,
|
|
33
|
+
DatePropertyFilter,
|
|
34
|
+
DatePropertyFilterValue,
|
|
35
|
+
DatePropertyTypes,
|
|
36
|
+
ExactDatePropertyFilterValue,
|
|
37
|
+
ExactRangeDatePropertyFilterValue,
|
|
38
|
+
FeedViewSchema,
|
|
39
|
+
FilterSchema,
|
|
40
|
+
FormulaGroupByFormat,
|
|
41
|
+
GalleryViewSchema,
|
|
42
|
+
GroupByFormat,
|
|
43
|
+
GroupByFormatBase,
|
|
44
|
+
GroupFormat,
|
|
45
|
+
ListViewSchema,
|
|
46
|
+
LocationGroupByFormat,
|
|
47
|
+
MultiSelectPropertyFilter,
|
|
48
|
+
NumberGroupByFormat,
|
|
49
|
+
NumberPropertyFilter,
|
|
50
|
+
PersonGroupByFormat,
|
|
51
|
+
PersonPropertyFilter,
|
|
52
|
+
PropertyFilterSchema,
|
|
53
|
+
PropertyFormat,
|
|
54
|
+
PropertyViewSortSchema,
|
|
55
|
+
PropertyVisibility,
|
|
56
|
+
RelationGroupByFormat,
|
|
57
|
+
RelationPropertyFilter,
|
|
58
|
+
RelativeDatePreset,
|
|
59
|
+
RelativeDatePropertyFilterValue,
|
|
60
|
+
RelativeDateRangeUnit,
|
|
61
|
+
RelativeToTodayDatePropertyFilterValue,
|
|
62
|
+
SelectGroupByFormat,
|
|
63
|
+
SelectPropertyFilter,
|
|
64
|
+
StatusGroupByFormat,
|
|
65
|
+
StatusPropertyFilter,
|
|
66
|
+
TableViewSchema,
|
|
67
|
+
TextGroupByFormat,
|
|
68
|
+
TextPropertyFilter,
|
|
69
|
+
TimelineViewSchema,
|
|
70
|
+
ViewSchema,
|
|
71
|
+
ViewType,
|
|
72
|
+
} from "./views.js";
|
|
14
73
|
|
|
15
74
|
/** Experimental Notion-as-Code facade for Apps SDK project metadata. */
|
|
16
75
|
export const notion = createNotion();
|
|
@@ -2,6 +2,7 @@ import type { CustomAgentArgs } from "./custom-agent.js";
|
|
|
2
2
|
import type { DatabaseArgs } from "./database.js";
|
|
3
3
|
import type { PageArgs } from "./page.js";
|
|
4
4
|
import type { TeamspaceArgs } from "./teamspace.js";
|
|
5
|
+
import type { ViewSchema } from "./views.js";
|
|
5
6
|
|
|
6
7
|
/** Apps-specific resource IDs used while declaring Notion-as-Code metadata. */
|
|
7
8
|
/** Synthetic workspace parent for teamspaces declared by an Apps project. */
|
|
@@ -116,8 +117,7 @@ export type CustomAgentIntent = CustomAgentArgs;
|
|
|
116
117
|
|
|
117
118
|
export type ViewIntent = {
|
|
118
119
|
databaseResourceId: ResourceId;
|
|
119
|
-
|
|
120
|
-
view: unknown;
|
|
120
|
+
view: ViewSchema;
|
|
121
121
|
};
|
|
122
122
|
|
|
123
123
|
export type InfraAsCodeIntent =
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
APPS_WORKSPACE_RESOURCE_ID,
|
|
3
|
-
type DataSourceDefinition,
|
|
4
3
|
type PageIntent,
|
|
5
4
|
type Parent,
|
|
6
5
|
type ResourceId,
|
|
7
6
|
} from "./intents.js";
|
|
8
|
-
import {
|
|
7
|
+
import { createChildDatabase, type ChildDatabaseFactory } from "./database.js";
|
|
9
8
|
import { recordIntent } from "./recorder.js";
|
|
10
9
|
import { assertParent, assertUserResourceId } from "./resource.js";
|
|
11
10
|
|
|
@@ -25,9 +24,7 @@ export type ChildPageArgs = Omit<PageArgs, "parent">;
|
|
|
25
24
|
|
|
26
25
|
export type PageHandle = {
|
|
27
26
|
readonly resourceId: string;
|
|
28
|
-
addDatabase:
|
|
29
|
-
args: ChildDatabaseArgs<DataSources>,
|
|
30
|
-
) => DatabaseHandle<DataSources>;
|
|
27
|
+
addDatabase: ChildDatabaseFactory;
|
|
31
28
|
addPage: (args: ChildPageArgs) => PageHandle;
|
|
32
29
|
};
|
|
33
30
|
|
|
@@ -54,12 +51,7 @@ export function createPage(args: ChildPageArgs | PageArgs, parent: Parent): Page
|
|
|
54
51
|
|
|
55
52
|
return {
|
|
56
53
|
resourceId: args.resourceId,
|
|
57
|
-
addDatabase(
|
|
58
|
-
return database({
|
|
59
|
-
...databaseArgs,
|
|
60
|
-
parent: { type: "resourceId", resourceId: args.resourceId },
|
|
61
|
-
});
|
|
62
|
-
},
|
|
54
|
+
addDatabase: createChildDatabase({ type: "resourceId", resourceId: args.resourceId }),
|
|
63
55
|
addPage(pageArgs) {
|
|
64
56
|
return createPage(pageArgs, { type: "resourceId", resourceId: args.resourceId });
|
|
65
57
|
},
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
APPS_WORKSPACE_RESOURCE_ID,
|
|
3
|
-
type DataSourceDefinition,
|
|
4
3
|
type NotionAsCodeIcon,
|
|
5
4
|
type ResourceId,
|
|
6
5
|
type TeamspaceIntent,
|
|
7
6
|
} from "./intents.js";
|
|
8
|
-
import {
|
|
7
|
+
import { createChildDatabase, type ChildDatabaseFactory } from "./database.js";
|
|
9
8
|
import { createPage, type ChildPageArgs, type PageHandle } from "./page.js";
|
|
10
9
|
import { recordIntent } from "./recorder.js";
|
|
11
10
|
import { assertUserResourceId } from "./resource.js";
|
|
@@ -20,9 +19,7 @@ export type TeamspaceArgs = {
|
|
|
20
19
|
|
|
21
20
|
export type TeamspaceHandle = {
|
|
22
21
|
readonly resourceId: string;
|
|
23
|
-
addDatabase:
|
|
24
|
-
args: ChildDatabaseArgs<DataSources>,
|
|
25
|
-
) => DatabaseHandle<DataSources>;
|
|
22
|
+
addDatabase: ChildDatabaseFactory;
|
|
26
23
|
addPage: (args: ChildPageArgs) => PageHandle;
|
|
27
24
|
};
|
|
28
25
|
|
|
@@ -37,12 +34,7 @@ export function teamspace(args: TeamspaceArgs): TeamspaceHandle {
|
|
|
37
34
|
|
|
38
35
|
return {
|
|
39
36
|
resourceId: args.resourceId,
|
|
40
|
-
addDatabase(
|
|
41
|
-
return database({
|
|
42
|
-
...databaseArgs,
|
|
43
|
-
parent: { type: "resourceId", resourceId: args.resourceId },
|
|
44
|
-
});
|
|
45
|
-
},
|
|
37
|
+
addDatabase: createChildDatabase({ type: "resourceId", resourceId: args.resourceId }),
|
|
46
38
|
addPage(pageArgs) {
|
|
47
39
|
return createPage(pageArgs, { type: "resourceId", resourceId: args.resourceId });
|
|
48
40
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ResourceId } from "./intents.js";
|
|
2
|
+
import { recordIntent } from "./recorder.js";
|
|
3
|
+
import { assertUserResourceId } from "./resource.js";
|
|
4
|
+
import type { ViewSchema } from "./views.js";
|
|
5
|
+
|
|
6
|
+
/** Declare a view attached to a database resource. */
|
|
7
|
+
export type ViewArgs = ViewSchema & {
|
|
8
|
+
databaseResourceId: ResourceId;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ViewHandle = {
|
|
12
|
+
readonly resourceId: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Record a database view for provisioning without making a Notion API request. */
|
|
16
|
+
export function view(args: ViewArgs): ViewHandle {
|
|
17
|
+
assertUserResourceId(args.resourceId);
|
|
18
|
+
assertUserResourceId(args.databaseResourceId);
|
|
19
|
+
assertUserResourceId(args.dataSourceResourceId);
|
|
20
|
+
const { databaseResourceId, ...schema } = args;
|
|
21
|
+
recordIntent({ type: "view", databaseResourceId, view: schema });
|
|
22
|
+
return { resourceId: args.resourceId };
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expectTypeOf, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
DatabaseArgs,
|
|
5
|
+
DatabaseHandle,
|
|
6
|
+
DatePropertyFilter,
|
|
7
|
+
GroupByFormat,
|
|
8
|
+
ViewSchema,
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
|
|
11
|
+
it("requires date properties for calendar and timeline views at both entrypoints", () => {
|
|
12
|
+
type MissingDateProperty = {
|
|
13
|
+
resourceId: "view";
|
|
14
|
+
dataSourceResourceId: "source";
|
|
15
|
+
type: "calendar" | "timeline";
|
|
16
|
+
};
|
|
17
|
+
expectTypeOf<MissingDateProperty>().not.toExtend<ViewSchema>();
|
|
18
|
+
expectTypeOf<MissingDateProperty>().not.toExtend<NonNullable<DatabaseArgs["views"]>[number]>();
|
|
19
|
+
expectTypeOf<MissingDateProperty>().not.toExtend<Parameters<DatabaseHandle["addView"]>[0]>();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("couples date operators to their value shapes and requires a range bound", () => {
|
|
23
|
+
type DateFilter = {
|
|
24
|
+
type: "property";
|
|
25
|
+
propertyId: "due";
|
|
26
|
+
propertyType: "date";
|
|
27
|
+
};
|
|
28
|
+
expectTypeOf<
|
|
29
|
+
DateFilter & {
|
|
30
|
+
operator: "date_is_within";
|
|
31
|
+
value: { type: "exact_range"; value: { startDate: "2026-01-01" } };
|
|
32
|
+
}
|
|
33
|
+
>().toExtend<DatePropertyFilter>();
|
|
34
|
+
expectTypeOf<
|
|
35
|
+
DateFilter & {
|
|
36
|
+
operator: "date_is_within";
|
|
37
|
+
value: { type: "exact_range"; value: {} };
|
|
38
|
+
}
|
|
39
|
+
>().not.toExtend<DatePropertyFilter>();
|
|
40
|
+
expectTypeOf<
|
|
41
|
+
DateFilter & {
|
|
42
|
+
operator: "date_is_relative_to";
|
|
43
|
+
value: { type: "exact"; value: "2026-01-01" };
|
|
44
|
+
}
|
|
45
|
+
>().not.toExtend<DatePropertyFilter>();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("limits status grouping options to status properties", () => {
|
|
49
|
+
expectTypeOf<{
|
|
50
|
+
type: "status";
|
|
51
|
+
property: "status";
|
|
52
|
+
statusBy: "group";
|
|
53
|
+
}>().toExtend<GroupByFormat>();
|
|
54
|
+
expectTypeOf<{
|
|
55
|
+
type: "select";
|
|
56
|
+
property: "status";
|
|
57
|
+
statusBy: "group";
|
|
58
|
+
}>().not.toExtend<GroupByFormat>();
|
|
59
|
+
});
|