@notionhq/apps 0.0.17 → 0.0.19
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 +27 -0
- package/README.md +77 -35
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +12 -7
- package/dist/cli/emit-manifest.d.ts +1 -1
- package/dist/cli/emit-manifest.d.ts.map +1 -1
- package/dist/cli/emit-manifest.js +66 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/notion-as-code/custom-agent.d.ts +1 -0
- package/dist/notion-as-code/custom-agent.d.ts.map +1 -1
- package/dist/notion-as-code/custom-agent.js +1 -1
- package/dist/notion-as-code/database.d.ts +90 -48
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +135 -45
- package/dist/notion-as-code/database.test.d.ts +2 -0
- package/dist/notion-as-code/database.test.d.ts.map +1 -0
- package/dist/notion-as-code/index.d.ts +2 -2
- package/dist/notion-as-code/index.d.ts.map +1 -1
- package/dist/notion-as-code/intents.d.ts +34 -2
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/page.d.ts +1 -0
- package/dist/notion-as-code/page.d.ts.map +1 -1
- package/dist/notion-as-code/page.js +1 -0
- package/dist/notion-as-code/schema.d.ts +8 -4
- package/dist/notion-as-code/schema.d.ts.map +1 -1
- package/dist/notion-as-code/views.d.ts +29 -10
- package/dist/notion-as-code/views.d.ts.map +1 -1
- package/dist/sync.d.ts +33 -15
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +12 -0
- package/dist/workflow-access-types.test.d.ts +2 -0
- package/dist/workflow-access-types.test.d.ts.map +1 -0
- package/dist/workflow-access.d.ts +68 -0
- package/dist/workflow-access.d.ts.map +1 -0
- package/dist/workflow-access.js +135 -0
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.d.ts.map +1 -0
- package/dist/workflow-state.js +107 -0
- package/dist/workflow.d.ts +65 -9
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +119 -5
- package/docs/BUILD.md +134 -4
- package/package.json +1 -1
- package/skills/notion-as-code/SKILL.md +89 -49
- package/skills/sync/SKILL.md +21 -18
- package/skills/workflow/SKILL.md +101 -17
- package/src/cli/build.test.ts +148 -64
- package/src/cli/build.ts +13 -8
- package/src/cli/emit-manifest.ts +107 -0
- package/src/index.ts +21 -1
- package/src/notion-as-code/custom-agent.ts +2 -1
- package/src/notion-as-code/database.test.ts +661 -0
- package/src/notion-as-code/database.ts +349 -127
- package/src/notion-as-code/index.ts +16 -1
- package/src/notion-as-code/intents.ts +39 -2
- package/src/notion-as-code/page.ts +2 -0
- package/src/notion-as-code/schema.ts +11 -3
- package/src/notion-as-code/views.ts +29 -10
- package/src/sync.test.ts +295 -0
- package/src/sync.ts +85 -21
- package/src/workflow-access-types.test.ts +69 -0
- package/src/workflow-access.ts +299 -0
- package/src/workflow-state.ts +152 -0
- package/src/workflow.test.ts +441 -1
- package/src/workflow.ts +230 -13
|
@@ -1,197 +1,418 @@
|
|
|
1
1
|
import type { Database } from "../database.js";
|
|
2
2
|
import {
|
|
3
3
|
APPS_WORKSPACE_RESOURCE_ID,
|
|
4
|
-
type DataSourceDefinition,
|
|
5
4
|
type DatabaseIntent,
|
|
5
|
+
type DataSourceDefinition,
|
|
6
6
|
type NotionAsCodeIcon,
|
|
7
7
|
type NotionAsCodeProperty,
|
|
8
|
+
type NotionAsCodeSchema,
|
|
8
9
|
type Parent,
|
|
9
10
|
type ResourceId,
|
|
10
11
|
} from "./intents.js";
|
|
11
12
|
import { createPage, type ChildPageArgs, type PageHandle } from "./page.js";
|
|
12
13
|
import { recordIntent } from "./recorder.js";
|
|
13
14
|
import { assertParent, assertUserResourceId } from "./resource.js";
|
|
14
|
-
import { createAppsDatabase, type
|
|
15
|
+
import { createAppsDatabase, type AppsSchemaForSchema } from "./schema.js";
|
|
15
16
|
import type { ViewSchema } from "./views.js";
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** Reuse serialized database intent metadata, excluding identity, parent, and sources. */
|
|
19
|
+
type DatabaseMetadata = Omit<DatabaseIntent, "resourceId" | "parent" | "dataSources">;
|
|
20
|
+
|
|
21
|
+
type NonEmptyViewSchemas = readonly [ViewSchema, ...ViewSchema[]];
|
|
22
|
+
|
|
23
|
+
/** A keyed data-source declaration; the containing map key supplies the serialized source name. */
|
|
24
|
+
export type DataSourceArgs<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
21
25
|
resourceId: ResourceId;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dataSources?: DataSources;
|
|
25
|
-
views?: readonly ViewSchema[];
|
|
26
|
+
name?: never;
|
|
27
|
+
schema: Schema;
|
|
26
28
|
icon?: NotionAsCodeIcon;
|
|
27
|
-
// TODO: Port cover types.
|
|
28
|
-
cover?: unknown;
|
|
29
|
-
hideDataSourceTitle?: boolean;
|
|
30
|
-
hideDatabaseTitleIfEmpty?: boolean;
|
|
31
|
-
name?: string;
|
|
32
|
-
description?: string;
|
|
33
29
|
};
|
|
34
30
|
|
|
35
|
-
/**
|
|
36
|
-
export type
|
|
37
|
-
DataSources extends readonly DataSourceDefinition[] = readonly DataSourceDefinition[],
|
|
38
|
-
> = Omit<DatabaseArgs<DataSources>, "parent">;
|
|
31
|
+
/** Data sources keyed by the names used to access their handles. */
|
|
32
|
+
export type DataSourceMap = Record<string, DataSourceArgs>;
|
|
39
33
|
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Single-source inputs require a name for both database and source. `datasources?: never`
|
|
36
|
+
* keeps this form exclusive even when callers pass a predeclared object.
|
|
37
|
+
*/
|
|
38
|
+
type SingleSourceDatabaseOptions<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = Omit<
|
|
39
|
+
DatabaseMetadata,
|
|
40
|
+
"name"
|
|
41
|
+
> & {
|
|
42
|
+
dataSourceResourceId: ResourceId;
|
|
44
43
|
name: string;
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
schema: Schema;
|
|
45
|
+
datasources?: never;
|
|
47
46
|
};
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
>
|
|
48
|
+
/** Top-level single-source inputs may choose a parent; page.addDatabase/teamspace.addDatabase bind it instead. */
|
|
49
|
+
export type SingleSourceDatabaseArgs<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
50
|
+
SingleSourceDatabaseOptions<Schema> & {
|
|
51
|
+
parent?: Parent;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Multi-source inputs preserve source-map keys. The NoInfer guard makes the
|
|
56
|
+
* emptiness check inspect the inferred map instead of widening it to the broad
|
|
57
|
+
* DataSourceMap constraint.
|
|
58
|
+
*/
|
|
59
|
+
type MultiSourceDatabaseOptions<DataSources extends DataSourceMap = DataSourceMap> =
|
|
60
|
+
DatabaseMetadata & {
|
|
61
|
+
datasources: DataSources;
|
|
62
|
+
dataSourceResourceId?: never;
|
|
63
|
+
schema?: never;
|
|
64
|
+
} & (keyof NoInfer<DataSources> extends never
|
|
65
|
+
? { views: NonEmptyViewSchemas }
|
|
66
|
+
: { views?: readonly ViewSchema[] });
|
|
67
|
+
|
|
68
|
+
/** Top-level multi-source inputs may choose a parent; page.addDatabase/teamspace.addDatabase bind it instead. */
|
|
69
|
+
export type MultiSourceDatabaseArgs<DataSources extends DataSourceMap = DataSourceMap> =
|
|
70
|
+
MultiSourceDatabaseOptions<DataSources> & {
|
|
71
|
+
parent?: Parent;
|
|
72
|
+
};
|
|
52
73
|
|
|
53
|
-
/**
|
|
54
|
-
type
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
/** Linked-only databases use a nonempty typed view tuple and no authored source map. */
|
|
75
|
+
type ViewOnlyDatabaseOptions = DatabaseMetadata & {
|
|
76
|
+
datasources?: never;
|
|
77
|
+
dataSourceResourceId?: never;
|
|
78
|
+
schema?: never;
|
|
79
|
+
views: NonEmptyViewSchemas;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type ViewOnlyDatabaseArgs = ViewOnlyDatabaseOptions & {
|
|
83
|
+
parent?: Parent;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Remove parent because page.addDatabase/teamspace.addDatabase supplies it. */
|
|
87
|
+
export type ChildSingleSourceDatabaseArgs<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
88
|
+
Omit<SingleSourceDatabaseArgs<Schema>, "parent">;
|
|
89
|
+
|
|
90
|
+
/** Remove parent because page.addDatabase/teamspace.addDatabase supplies it. */
|
|
91
|
+
export type ChildMultiSourceDatabaseArgs<DataSources extends DataSourceMap = DataSourceMap> = Omit<
|
|
92
|
+
MultiSourceDatabaseArgs<DataSources>,
|
|
93
|
+
"parent"
|
|
94
|
+
>;
|
|
95
|
+
|
|
96
|
+
/** Input for declaring a database and its data sources. */
|
|
97
|
+
export type DatabaseArgs<
|
|
98
|
+
Schema extends NotionAsCodeSchema = NotionAsCodeSchema,
|
|
99
|
+
DataSources extends DataSourceMap = DataSourceMap,
|
|
100
|
+
> = SingleSourceDatabaseArgs<Schema> | MultiSourceDatabaseArgs<DataSources> | ViewOnlyDatabaseArgs;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Child inputs omit parent from each union member separately. `page.addDatabase`
|
|
104
|
+
* and `teamspace.addDatabase` use this shape for their shared child factory.
|
|
105
|
+
*/
|
|
106
|
+
export type ChildDatabaseArgs<
|
|
107
|
+
Schema extends NotionAsCodeSchema = NotionAsCodeSchema,
|
|
108
|
+
DataSources extends DataSourceMap = DataSourceMap,
|
|
57
109
|
> =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
export type DataSourceHandle<
|
|
66
|
-
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
67
|
-
> = {
|
|
110
|
+
| ChildSingleSourceDatabaseArgs<Schema>
|
|
111
|
+
| ChildMultiSourceDatabaseArgs<DataSources>
|
|
112
|
+
| Omit<ViewOnlyDatabaseArgs, "parent">;
|
|
113
|
+
|
|
114
|
+
/** A source handle retains its keyed authoring schema for sync inference. */
|
|
115
|
+
export type DataSourceHandle<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> = {
|
|
116
|
+
readonly resourceType: "dataSource";
|
|
68
117
|
readonly resourceId: string;
|
|
69
|
-
readonly schema:
|
|
70
|
-
readonly database: Database<
|
|
118
|
+
readonly schema: Schema;
|
|
119
|
+
readonly database: Database<AppsSchemaForSchema<Schema>>;
|
|
71
120
|
addPage: (args: ChildPageArgs) => PageHandle;
|
|
72
121
|
};
|
|
73
122
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
DataSources extends readonly DataSourceDefinition[] = readonly DataSourceDefinition[],
|
|
77
|
-
> = {
|
|
123
|
+
type DatabaseHandleBase = {
|
|
124
|
+
readonly resourceType: "database";
|
|
78
125
|
readonly resourceId: string;
|
|
79
|
-
readonly dataSources: {
|
|
80
|
-
readonly [ResourceId in DataSources[number]["resourceId"]]: DataSourceHandle<
|
|
81
|
-
DataSourcePropertiesForResourceId<DataSources, ResourceId>
|
|
82
|
-
>;
|
|
83
|
-
};
|
|
84
126
|
addView: (view: ViewSchema) => void;
|
|
85
127
|
};
|
|
86
128
|
|
|
87
|
-
/**
|
|
88
|
-
export type
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
/** Map each authoring source key to a handle backed by that source's own schema. */
|
|
130
|
+
export type DatabaseHandle<DataSources extends DataSourceMap = DataSourceMap> =
|
|
131
|
+
DatabaseHandleBase & {
|
|
132
|
+
readonly datasources: {
|
|
133
|
+
readonly [DataSourceName in keyof DataSources]: DataSourceHandle<
|
|
134
|
+
DataSources[DataSourceName]["schema"]
|
|
135
|
+
>;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
93
138
|
|
|
139
|
+
/** A database whose sole data source is available without a key lookup. */
|
|
140
|
+
export type SingleSourceDatabaseHandle<Schema extends NotionAsCodeSchema = NotionAsCodeSchema> =
|
|
141
|
+
DatabaseHandleBase & {
|
|
142
|
+
readonly dataSource: DataSourceHandle<Schema>;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Overloaded callable used by page.addDatabase/teamspace.addDatabase. Each form
|
|
147
|
+
* returns its matching handle while preserving literal source and schema inference.
|
|
148
|
+
*/
|
|
94
149
|
export type ChildDatabaseFactory = {
|
|
95
|
-
<const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
150
|
+
<const Schema extends NotionAsCodeSchema>(
|
|
151
|
+
databaseResourceId: ResourceId,
|
|
152
|
+
args: ChildSingleSourceDatabaseArgs<Schema>,
|
|
153
|
+
): SingleSourceDatabaseHandle<Schema>;
|
|
154
|
+
<const DataSources extends DataSourceMap>(
|
|
155
|
+
databaseResourceId: ResourceId,
|
|
156
|
+
args: ChildMultiSourceDatabaseArgs<DataSources>,
|
|
100
157
|
): DatabaseHandle<DataSources>;
|
|
158
|
+
(
|
|
159
|
+
databaseResourceId: ResourceId,
|
|
160
|
+
args: Omit<ViewOnlyDatabaseArgs, "parent">,
|
|
161
|
+
): DatabaseHandle<{}>;
|
|
101
162
|
};
|
|
102
163
|
|
|
103
|
-
/** Declare a database, defaulting to
|
|
104
|
-
export function database<const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
164
|
+
/** Declare a database, defaulting to the Apps workspace. */
|
|
165
|
+
export function database<const Schema extends NotionAsCodeSchema>(
|
|
166
|
+
databaseResourceId: ResourceId,
|
|
167
|
+
args: SingleSourceDatabaseArgs<Schema>,
|
|
168
|
+
): SingleSourceDatabaseHandle<Schema>;
|
|
169
|
+
export function database<const DataSources extends DataSourceMap>(
|
|
170
|
+
databaseResourceId: ResourceId,
|
|
171
|
+
args: MultiSourceDatabaseArgs<DataSources>,
|
|
109
172
|
): DatabaseHandle<DataSources>;
|
|
110
173
|
export function database(
|
|
111
|
-
|
|
174
|
+
databaseResourceId: ResourceId,
|
|
175
|
+
args: ViewOnlyDatabaseArgs,
|
|
176
|
+
): DatabaseHandle<{}>;
|
|
177
|
+
export function database(
|
|
178
|
+
databaseResourceId: ResourceId,
|
|
179
|
+
args: DatabaseArgs,
|
|
112
180
|
): DatabaseHandle | SingleSourceDatabaseHandle {
|
|
113
|
-
if (args.
|
|
114
|
-
|
|
181
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
182
|
+
throw new Error("Notion-as-Code database arguments must be an object");
|
|
115
183
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
184
|
+
const parentValue = args.parent;
|
|
185
|
+
if (parentValue !== undefined) {
|
|
186
|
+
assertParent(parentValue);
|
|
187
|
+
}
|
|
188
|
+
const parent = parentValue ?? {
|
|
189
|
+
type: "resourceId" as const,
|
|
118
190
|
resourceId: APPS_WORKSPACE_RESOURCE_ID,
|
|
119
191
|
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
192
|
+
return buildDatabaseHandle(databaseResourceId, args, parent);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Bind a parent for page.addDatabase/teamspace.addDatabase. */
|
|
196
|
+
export function createChildDatabase(parent: Parent): ChildDatabaseFactory {
|
|
197
|
+
assertParent(parent);
|
|
198
|
+
|
|
199
|
+
function addDatabase<const Schema extends NotionAsCodeSchema>(
|
|
200
|
+
databaseResourceId: ResourceId,
|
|
201
|
+
args: ChildSingleSourceDatabaseArgs<Schema>,
|
|
202
|
+
): SingleSourceDatabaseHandle<Schema>;
|
|
203
|
+
function addDatabase<const DataSources extends DataSourceMap>(
|
|
204
|
+
databaseResourceId: ResourceId,
|
|
205
|
+
args: ChildMultiSourceDatabaseArgs<DataSources>,
|
|
206
|
+
): DatabaseHandle<DataSources>;
|
|
207
|
+
function addDatabase(
|
|
208
|
+
databaseResourceId: ResourceId,
|
|
209
|
+
args: Omit<ViewOnlyDatabaseArgs, "parent">,
|
|
210
|
+
): DatabaseHandle<{}>;
|
|
211
|
+
function addDatabase(
|
|
212
|
+
databaseResourceId: ResourceId,
|
|
213
|
+
args: ChildDatabaseArgs,
|
|
214
|
+
): DatabaseHandle | SingleSourceDatabaseHandle {
|
|
215
|
+
return buildDatabaseHandle(databaseResourceId, args, parent);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return addDatabase;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Internal bridge pairing an authoring key/schema with its serialized definition
|
|
222
|
+
* during database recording and handle creation; never serialized itself.
|
|
223
|
+
*/
|
|
224
|
+
type SourceEntry = {
|
|
225
|
+
key: string;
|
|
226
|
+
schema: NotionAsCodeSchema;
|
|
227
|
+
definition: DataSourceDefinition;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Build a database handle from one of the authoring forms. Form checks happen
|
|
232
|
+
* before normalization or recording so mixed and malformed declarations are atomic.
|
|
233
|
+
*/
|
|
234
|
+
function buildDatabaseHandle(
|
|
235
|
+
databaseResourceId: ResourceId,
|
|
236
|
+
args: DatabaseArgs,
|
|
237
|
+
parent: Parent,
|
|
238
|
+
): DatabaseHandle | SingleSourceDatabaseHandle {
|
|
239
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
240
|
+
throw new Error("Notion-as-Code database arguments must be an object");
|
|
241
|
+
}
|
|
242
|
+
if (typeof databaseResourceId !== "string") {
|
|
243
|
+
throw new Error("Notion-as-Code database resourceId must be a string");
|
|
244
|
+
}
|
|
245
|
+
assertUserResourceId(databaseResourceId);
|
|
246
|
+
|
|
247
|
+
const hasSchema = Object.prototype.hasOwnProperty.call(args, "schema");
|
|
248
|
+
const hasDataSources = Object.prototype.hasOwnProperty.call(args, "datasources");
|
|
249
|
+
if (hasSchema && hasDataSources) {
|
|
250
|
+
throw new Error("Specify either schema or datasources, not both");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (args.schema !== undefined) {
|
|
125
254
|
const source: DataSourceDefinition = {
|
|
126
|
-
resourceId:
|
|
255
|
+
resourceId: args.dataSourceResourceId,
|
|
127
256
|
name: args.name,
|
|
128
|
-
properties,
|
|
257
|
+
properties: normalizeProperties(args.schema),
|
|
129
258
|
};
|
|
130
|
-
const handle =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
259
|
+
const handle = recordDatabase(databaseResourceId, args, parent, [
|
|
260
|
+
{ key: "dataSource", schema: args.schema, definition: source },
|
|
261
|
+
]);
|
|
262
|
+
const dataSource = handle.datasources.dataSource;
|
|
263
|
+
if (dataSource === undefined) {
|
|
264
|
+
throw new Error(`Database "${databaseResourceId}" is missing its data source`);
|
|
134
265
|
}
|
|
135
|
-
return {
|
|
266
|
+
return {
|
|
267
|
+
resourceType: "database",
|
|
268
|
+
resourceId: databaseResourceId,
|
|
269
|
+
dataSource,
|
|
270
|
+
addView: handle.addView,
|
|
271
|
+
};
|
|
136
272
|
}
|
|
137
|
-
|
|
273
|
+
|
|
274
|
+
if (args.datasources !== undefined) {
|
|
275
|
+
if (
|
|
276
|
+
typeof args.datasources !== "object" ||
|
|
277
|
+
args.datasources === null ||
|
|
278
|
+
Array.isArray(args.datasources)
|
|
279
|
+
) {
|
|
280
|
+
throw new Error("Notion-as-Code datasources must be an object");
|
|
281
|
+
}
|
|
282
|
+
const sourceEntries = Object.entries(args.datasources).map(([key, sourceArgs]) =>
|
|
283
|
+
normalizeDataSource(key, sourceArgs),
|
|
284
|
+
);
|
|
285
|
+
return recordDatabase(databaseResourceId, args, parent, sourceEntries);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return recordDatabase(databaseResourceId, args, parent, []);
|
|
138
289
|
}
|
|
139
290
|
|
|
140
|
-
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
291
|
+
/** Strip authoring-only fields from database arguments before serializing metadata. */
|
|
292
|
+
function normalizeDatabaseMetadata(args: DatabaseArgs): DatabaseMetadata {
|
|
293
|
+
const {
|
|
294
|
+
parent: _parent,
|
|
295
|
+
schema: _schema,
|
|
296
|
+
datasources: _datasources,
|
|
297
|
+
dataSourceResourceId: _dataSourceResourceId,
|
|
298
|
+
...metadata
|
|
299
|
+
} = args;
|
|
300
|
+
return metadata;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Normalize a keyed declaration into its serialized definition while retaining key/schema for handles. */
|
|
304
|
+
function normalizeDataSource(key: string, sourceArgs: DataSourceArgs): SourceEntry {
|
|
305
|
+
if (typeof sourceArgs !== "object" || sourceArgs === null || Array.isArray(sourceArgs)) {
|
|
306
|
+
throw new Error(`Data source "${key}" must be an object`);
|
|
307
|
+
}
|
|
308
|
+
const { schema, ...sourceMetadata } = sourceArgs;
|
|
309
|
+
return {
|
|
310
|
+
key,
|
|
311
|
+
schema,
|
|
312
|
+
definition: {
|
|
313
|
+
...sourceMetadata,
|
|
314
|
+
name: key,
|
|
315
|
+
properties: normalizeProperties(schema),
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Normalize keyed property configurations to the serialized property array.
|
|
322
|
+
* Property names always come from schema keys; authoring name fields are ignored.
|
|
323
|
+
*/
|
|
324
|
+
function normalizeProperties(schema: NotionAsCodeSchema): NotionAsCodeProperty[] {
|
|
325
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
|
326
|
+
throw new Error("Notion-as-Code schema must be an object");
|
|
327
|
+
}
|
|
328
|
+
const properties: NotionAsCodeProperty[] = [];
|
|
329
|
+
for (const [key, propertyConfig] of Object.entries(schema)) {
|
|
330
|
+
if (
|
|
331
|
+
typeof propertyConfig !== "object" ||
|
|
332
|
+
propertyConfig === null ||
|
|
333
|
+
Array.isArray(propertyConfig)
|
|
334
|
+
) {
|
|
335
|
+
throw new Error(`Property "${key}" must be an object`);
|
|
153
336
|
}
|
|
154
|
-
|
|
337
|
+
if (typeof propertyConfig.resourceId !== "string") {
|
|
338
|
+
throw new Error(`Property "${key}" must specify a resourceId`);
|
|
339
|
+
}
|
|
340
|
+
properties.push({
|
|
341
|
+
...propertyConfig,
|
|
342
|
+
name: key,
|
|
343
|
+
});
|
|
155
344
|
}
|
|
156
|
-
return
|
|
345
|
+
return properties;
|
|
157
346
|
}
|
|
158
347
|
|
|
159
|
-
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
348
|
+
/** Validate the views shape and require at least one data source or view. */
|
|
349
|
+
function assertSourceOrView(sourceEntries: readonly SourceEntry[], views: unknown): void {
|
|
350
|
+
if (views !== undefined && !Array.isArray(views)) {
|
|
351
|
+
throw new Error("Notion-as-Code database views must be an array");
|
|
352
|
+
}
|
|
353
|
+
if (sourceEntries.length === 0 && (!Array.isArray(views) || views.length === 0)) {
|
|
354
|
+
throw new Error("Notion-as-Code database must define a data source or view");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Record a normalized database intent and create handles for its data sources. */
|
|
359
|
+
function recordDatabase(
|
|
360
|
+
databaseResourceId: ResourceId,
|
|
361
|
+
args: DatabaseArgs,
|
|
362
|
+
parent: Parent,
|
|
363
|
+
sourceEntries: readonly SourceEntry[],
|
|
364
|
+
): DatabaseHandle {
|
|
365
|
+
const metadata = normalizeDatabaseMetadata(args);
|
|
366
|
+
assertSourceOrView(sourceEntries, metadata.views);
|
|
367
|
+
const intent: DatabaseIntent = {
|
|
368
|
+
...metadata,
|
|
369
|
+
resourceId: databaseResourceId,
|
|
370
|
+
parent,
|
|
371
|
+
dataSources: sourceEntries.map(({ definition }) => definition),
|
|
372
|
+
};
|
|
373
|
+
return createDatabase(intent, sourceEntries);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Create a normalized database handle while preserving each source's authoring schema. */
|
|
377
|
+
function createDatabase(
|
|
378
|
+
intent: DatabaseIntent,
|
|
379
|
+
sourceEntries: readonly SourceEntry[],
|
|
380
|
+
): DatabaseHandle {
|
|
381
|
+
assertUserResourceId(intent.resourceId);
|
|
163
382
|
const dataSourceIds = new Set<string>();
|
|
164
|
-
const handles: Record<string, DataSourceHandle> =
|
|
383
|
+
const handles: Record<string, DataSourceHandle> = Object.create(null);
|
|
165
384
|
|
|
166
|
-
for (const
|
|
167
|
-
|
|
168
|
-
|
|
385
|
+
for (const { key, schema, definition } of sourceEntries) {
|
|
386
|
+
if (typeof definition.resourceId !== "string") {
|
|
387
|
+
throw new Error(`Data source "${key}" must specify a resourceId`);
|
|
388
|
+
}
|
|
389
|
+
assertUserResourceId(definition.resourceId);
|
|
390
|
+
if (dataSourceIds.has(definition.resourceId)) {
|
|
169
391
|
throw new Error(
|
|
170
|
-
`Database "${
|
|
392
|
+
`Database "${intent.resourceId}" has duplicate data source "${definition.resourceId}"`,
|
|
171
393
|
);
|
|
172
394
|
}
|
|
173
|
-
dataSourceIds.add(
|
|
174
|
-
handles[
|
|
395
|
+
dataSourceIds.add(definition.resourceId);
|
|
396
|
+
handles[key] = createDataSourceHandle(definition, schema);
|
|
175
397
|
}
|
|
176
398
|
|
|
177
|
-
recordIntent({
|
|
178
|
-
type: "database",
|
|
179
|
-
...args,
|
|
180
|
-
parent,
|
|
181
|
-
dataSources: [...dataSources],
|
|
182
|
-
} satisfies { type: "database" } & DatabaseIntent);
|
|
399
|
+
recordIntent({ ...intent, type: "database" });
|
|
183
400
|
|
|
184
401
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
402
|
+
resourceType: "database",
|
|
403
|
+
resourceId: intent.resourceId,
|
|
404
|
+
datasources: handles,
|
|
187
405
|
addView(view) {
|
|
188
|
-
recordIntent({ type: "view", databaseResourceId:
|
|
406
|
+
recordIntent({ type: "view", databaseResourceId: intent.resourceId, view });
|
|
189
407
|
},
|
|
190
408
|
};
|
|
191
409
|
}
|
|
192
410
|
|
|
193
|
-
/** Create a data
|
|
194
|
-
function createDataSourceHandle(
|
|
411
|
+
/** Create a data-source handle backed by the Apps database used by syncs. */
|
|
412
|
+
function createDataSourceHandle(
|
|
413
|
+
dataSource: DataSourceDefinition,
|
|
414
|
+
schema: NotionAsCodeSchema,
|
|
415
|
+
): DataSourceHandle {
|
|
195
416
|
const propertyResourceIds = new Set<string>();
|
|
196
417
|
const propertyNames = new Set<string>();
|
|
197
418
|
for (const property of dataSource.properties) {
|
|
@@ -211,8 +432,9 @@ function createDataSourceHandle(dataSource: DataSourceDefinition): DataSourceHan
|
|
|
211
432
|
}
|
|
212
433
|
|
|
213
434
|
return {
|
|
435
|
+
resourceType: "dataSource",
|
|
214
436
|
resourceId: dataSource.resourceId,
|
|
215
|
-
schema
|
|
437
|
+
schema,
|
|
216
438
|
database: createAppsDatabase(dataSource.resourceId, dataSource),
|
|
217
439
|
addPage(pageArgs) {
|
|
218
440
|
return createPage(pageArgs, { type: "resourceId", resourceId: dataSource.resourceId });
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { createNotion } from "./handles.js";
|
|
2
2
|
|
|
3
3
|
export { APPS_RESERVED_RESOURCE_ID_PREFIX, APPS_WORKSPACE_RESOURCE_ID } from "./intents.js";
|
|
4
|
-
export type {
|
|
4
|
+
export type {
|
|
5
|
+
DatabaseIntent,
|
|
6
|
+
DataSourceDefinition,
|
|
7
|
+
NotionAsCodeIcon,
|
|
8
|
+
NotionAsCodeProperty,
|
|
9
|
+
NotionAsCodePropertyConfig,
|
|
10
|
+
NotionAsCodeSchema,
|
|
11
|
+
NotionAsCodeSelectOption,
|
|
12
|
+
NotionAsCodeStatusOption,
|
|
13
|
+
Parent,
|
|
14
|
+
ResourceId,
|
|
15
|
+
} from "./intents.js";
|
|
5
16
|
export type { CustomAgentArgs, CustomAgentHandle } from "./custom-agent.js";
|
|
6
17
|
export type {
|
|
7
18
|
ChildDatabaseArgs,
|
|
8
19
|
ChildDatabaseFactory,
|
|
20
|
+
ChildMultiSourceDatabaseArgs,
|
|
9
21
|
ChildSingleSourceDatabaseArgs,
|
|
22
|
+
DataSourceArgs,
|
|
10
23
|
DataSourceHandle,
|
|
24
|
+
DataSourceMap,
|
|
11
25
|
DatabaseArgs,
|
|
12
26
|
DatabaseHandle,
|
|
27
|
+
MultiSourceDatabaseArgs,
|
|
13
28
|
SingleSourceDatabaseArgs,
|
|
14
29
|
SingleSourceDatabaseHandle,
|
|
15
30
|
} from "./database.js";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CustomAgentArgs } from "./custom-agent.js";
|
|
2
|
-
import type { DatabaseArgs } from "./database.js";
|
|
3
2
|
import type { PageArgs } from "./page.js";
|
|
4
3
|
import type { TeamspaceArgs } from "./teamspace.js";
|
|
5
4
|
import type { ViewSchema } from "./views.js";
|
|
@@ -66,6 +65,7 @@ type UnsupportedProperty = PropertyBase & {
|
|
|
66
65
|
[key: string]: unknown;
|
|
67
66
|
};
|
|
68
67
|
|
|
68
|
+
/** A normalized Notion-as-Code property with its resolved display name. */
|
|
69
69
|
export type NotionAsCodeProperty =
|
|
70
70
|
| (PropertyBase & { type: "title" })
|
|
71
71
|
| (PropertyBase & { type: "text" })
|
|
@@ -92,6 +92,28 @@ export type NotionAsCodeProperty =
|
|
|
92
92
|
| (PropertyBase & { type: "file" })
|
|
93
93
|
| UnsupportedProperty;
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* `T extends unknown` applies Omit to each property variant separately, preserving
|
|
97
|
+
* variant-specific fields such as required status options. Plain Omit on the union
|
|
98
|
+
* would retain only shared keys. Pick also restores type/resourceId on the
|
|
99
|
+
* unsupported variant, whose string index signature would otherwise erase them.
|
|
100
|
+
*/
|
|
101
|
+
type DistributivePropertyConfig<T extends NotionAsCodeProperty> = T extends unknown
|
|
102
|
+
? Omit<T, "name"> & Pick<T, "type" | "resourceId">
|
|
103
|
+
: never;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Authoring properties are keyed by their schema object name. A property-level
|
|
107
|
+
* `name` override would make the key and serialized property diverge, so it is
|
|
108
|
+
* explicitly rejected even for the string-indexed unsupported-property variant.
|
|
109
|
+
*/
|
|
110
|
+
export type NotionAsCodePropertyConfig = DistributivePropertyConfig<NotionAsCodeProperty> & {
|
|
111
|
+
name?: never;
|
|
112
|
+
};
|
|
113
|
+
/** A keyed property schema, with object keys supplying serialized property names. */
|
|
114
|
+
export type NotionAsCodeSchema = Record<string, NotionAsCodePropertyConfig>;
|
|
115
|
+
|
|
116
|
+
/** The normalized data-source shape emitted in provisioning artifacts. */
|
|
95
117
|
export type DataSourceDefinition<
|
|
96
118
|
Properties extends readonly NotionAsCodeProperty[] = readonly NotionAsCodeProperty[],
|
|
97
119
|
> = {
|
|
@@ -107,9 +129,24 @@ export type TeamspaceIntent = TeamspaceArgs & {
|
|
|
107
129
|
parent: Parent;
|
|
108
130
|
};
|
|
109
131
|
|
|
110
|
-
|
|
132
|
+
/**
|
|
133
|
+
* The normalized database shape emitted in provisioning artifacts.
|
|
134
|
+
*
|
|
135
|
+
* This intentionally does not depend on the authoring argument shape. Authoring
|
|
136
|
+
* accepts keyed schemas and data-source maps; the recorder always stores arrays.
|
|
137
|
+
*/
|
|
138
|
+
export type DatabaseIntent = {
|
|
139
|
+
resourceId: ResourceId;
|
|
111
140
|
parent: Parent;
|
|
112
141
|
dataSources: readonly DataSourceDefinition[];
|
|
142
|
+
views?: readonly ViewSchema[];
|
|
143
|
+
icon?: NotionAsCodeIcon;
|
|
144
|
+
// TODO: Port cover types.
|
|
145
|
+
cover?: unknown;
|
|
146
|
+
hideDataSourceTitle?: boolean;
|
|
147
|
+
hideDatabaseTitleIfEmpty?: boolean;
|
|
148
|
+
name?: string;
|
|
149
|
+
description?: string;
|
|
113
150
|
};
|
|
114
151
|
|
|
115
152
|
export type PageIntent = PageArgs & { parent: Parent };
|
|
@@ -23,6 +23,7 @@ export type PageArgs = {
|
|
|
23
23
|
export type ChildPageArgs = Omit<PageArgs, "parent">;
|
|
24
24
|
|
|
25
25
|
export type PageHandle = {
|
|
26
|
+
readonly resourceType: "page";
|
|
26
27
|
readonly resourceId: string;
|
|
27
28
|
addDatabase: ChildDatabaseFactory;
|
|
28
29
|
addPage: (args: ChildPageArgs) => PageHandle;
|
|
@@ -50,6 +51,7 @@ export function createPage(args: ChildPageArgs | PageArgs, parent: Parent): Page
|
|
|
50
51
|
} satisfies { type: "page" } & PageIntent);
|
|
51
52
|
|
|
52
53
|
return {
|
|
54
|
+
resourceType: "page",
|
|
53
55
|
resourceId: args.resourceId,
|
|
54
56
|
addDatabase: createChildDatabase({ type: "resourceId", resourceId: args.resourceId }),
|
|
55
57
|
addPage(pageArgs) {
|