@notionhq/apps 0.0.17 → 0.0.18
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 +60 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/notion-as-code/database.d.ts +88 -48
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +132 -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/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-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 +48 -3
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +111 -4
- package/docs/BUILD.md +96 -0
- package/package.json +1 -1
- package/skills/notion-as-code/SKILL.md +81 -49
- package/skills/sync/SKILL.md +21 -18
- package/skills/workflow/SKILL.md +52 -3
- package/src/cli/build.test.ts +124 -64
- package/src/index.ts +1 -0
- package/src/notion-as-code/database.test.ts +661 -0
- package/src/notion-as-code/database.ts +344 -127
- package/src/notion-as-code/index.ts +16 -1
- package/src/notion-as-code/intents.ts +39 -2
- 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-state.ts +152 -0
- package/src/workflow.test.ts +329 -1
- package/src/workflow.ts +180 -6
package/src/cli/build.test.ts
CHANGED
|
@@ -15,17 +15,18 @@ import { finishMetadataRecording, startMetadataRecording } from "../notion-as-co
|
|
|
15
15
|
describe("Notion-as-Code database shorthand", () => {
|
|
16
16
|
it("keeps provisioning and sync identity equivalent to an explicit source", () => {
|
|
17
17
|
const recorder = startMetadataRecording();
|
|
18
|
-
const
|
|
19
|
-
resourceId: "
|
|
18
|
+
const schema = {
|
|
19
|
+
Name: { resourceId: "title", type: "title" },
|
|
20
|
+
Count: { resourceId: "count", type: "number" },
|
|
21
|
+
} as const;
|
|
22
|
+
const database = notion.database("issues", {
|
|
23
|
+
dataSourceResourceId: "issues-source",
|
|
20
24
|
name: "Issues",
|
|
21
|
-
|
|
22
|
-
{ resourceId: "title", name: "Name", type: "title" },
|
|
23
|
-
{ resourceId: "count", name: "Count", type: "number" },
|
|
24
|
-
],
|
|
25
|
+
schema,
|
|
25
26
|
});
|
|
26
|
-
expectTypeOf(database).toExtend<
|
|
27
|
-
|
|
28
|
-
>();
|
|
27
|
+
expectTypeOf(database).toExtend<SingleSourceDatabaseHandle<typeof schema>>();
|
|
28
|
+
expectTypeOf(database.dataSource.schema.Name.type).toEqualTypeOf<"title">();
|
|
29
|
+
expectTypeOf(database.dataSource.schema.Name.resourceId).toEqualTypeOf<"title">();
|
|
29
30
|
expectTypeOf(
|
|
30
31
|
database.dataSource.database.config.schema.Count.type,
|
|
31
32
|
).toEqualTypeOf<"number">();
|
|
@@ -33,18 +34,19 @@ describe("Notion-as-Code database shorthand", () => {
|
|
|
33
34
|
const shorthand = finishMetadataRecording(recorder).intents;
|
|
34
35
|
|
|
35
36
|
const explicitRecorder = startMetadataRecording();
|
|
36
|
-
const explicit = notion.database({
|
|
37
|
-
resourceId: "issues",
|
|
37
|
+
const explicit = notion.database("issues", {
|
|
38
38
|
name: "Issues",
|
|
39
|
-
|
|
40
|
-
{
|
|
39
|
+
datasources: {
|
|
40
|
+
Issues: {
|
|
41
41
|
resourceId: "issues-source",
|
|
42
|
-
|
|
43
|
-
properties: database.dataSource.schema,
|
|
42
|
+
schema,
|
|
44
43
|
},
|
|
45
|
-
|
|
44
|
+
},
|
|
46
45
|
});
|
|
47
|
-
explicit.
|
|
46
|
+
expectTypeOf(explicit.datasources.Issues).toExtend<DataSourceHandle<typeof schema>>();
|
|
47
|
+
expectTypeOf(explicit.datasources.Issues.schema.Name.type).toEqualTypeOf<"title">();
|
|
48
|
+
expectTypeOf(explicit.datasources.Issues.schema.Name.resourceId).toEqualTypeOf<"title">();
|
|
49
|
+
explicit.datasources.Issues.addPage({ resourceId: "issue" });
|
|
48
50
|
// @ts-expect-error Explicit databases do not guarantee a single source.
|
|
49
51
|
void explicit.dataSource;
|
|
50
52
|
expect(shorthand).toEqual(finishMetadataRecording(explicitRecorder).intents);
|
|
@@ -56,22 +58,24 @@ describe("Notion-as-Code database shorthand", () => {
|
|
|
56
58
|
});
|
|
57
59
|
});
|
|
58
60
|
|
|
59
|
-
it("preserves parent binding and
|
|
61
|
+
it("preserves parent binding and keyed source types through child helpers", () => {
|
|
60
62
|
const recorder = startMetadataRecording();
|
|
61
63
|
const page = notion.page({ resourceId: "page" });
|
|
62
64
|
const team = notion.teamspace({ resourceId: "team", name: "Team", accessLevel: "private" });
|
|
65
|
+
const schema = {
|
|
66
|
+
Title: { resourceId: "child-title", type: "title" },
|
|
67
|
+
} as const;
|
|
63
68
|
for (const parent of [page, team]) {
|
|
64
|
-
const database = parent.addDatabase({
|
|
65
|
-
|
|
69
|
+
const database = parent.addDatabase(`${parent.resourceId}-db`, {
|
|
70
|
+
dataSourceResourceId: `${parent.resourceId}-source`,
|
|
66
71
|
name: "Child",
|
|
67
|
-
|
|
68
|
-
{ resourceId: `${parent.resourceId}-title`, name: "Title", type: "title" },
|
|
69
|
-
],
|
|
72
|
+
schema,
|
|
70
73
|
});
|
|
71
|
-
expectTypeOf(database.dataSource).toExtend<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
expectTypeOf(database.dataSource).toExtend<DataSourceHandle<typeof schema>>();
|
|
75
|
+
expectTypeOf(database.dataSource.schema.Title.type).toEqualTypeOf<"title">();
|
|
76
|
+
expectTypeOf(
|
|
77
|
+
database.dataSource.schema.Title.resourceId,
|
|
78
|
+
).toEqualTypeOf<"child-title">();
|
|
75
79
|
}
|
|
76
80
|
const intents = finishMetadataRecording(recorder).intents;
|
|
77
81
|
expect(
|
|
@@ -82,19 +86,60 @@ describe("Notion-as-Code database shorthand", () => {
|
|
|
82
86
|
]);
|
|
83
87
|
});
|
|
84
88
|
|
|
85
|
-
it("
|
|
89
|
+
it("keeps keyed child inference and rejects empty child databases", () => {
|
|
86
90
|
const recorder = startMetadataRecording();
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
const page = notion.page({ resourceId: "child-page" });
|
|
92
|
+
const schema = {
|
|
93
|
+
Name: { type: "title", resourceId: "child-name" },
|
|
94
|
+
"Issue ID": { type: "text", resourceId: "child-issue-id" },
|
|
95
|
+
} as const;
|
|
96
|
+
const database = page.addDatabase("child-multi-database", {
|
|
97
|
+
datasources: {
|
|
98
|
+
Issues: {
|
|
99
|
+
resourceId: "child-issues-source",
|
|
100
|
+
schema,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
expectTypeOf(database.datasources.Issues).toExtend<DataSourceHandle<typeof schema>>();
|
|
105
|
+
expectTypeOf(
|
|
106
|
+
database.datasources.Issues.schema.Name.resourceId,
|
|
107
|
+
).toEqualTypeOf<"child-name">();
|
|
108
|
+
|
|
109
|
+
const invalidChildDeclarations = () => {
|
|
110
|
+
// @ts-expect-error Child databases need a source or a view.
|
|
111
|
+
page.addDatabase("child-empty-datasources", { datasources: {} });
|
|
112
|
+
// @ts-expect-error Child databases need a source or a view.
|
|
113
|
+
page.addDatabase("child-empty-arguments", {});
|
|
114
|
+
// @ts-expect-error Empty child sources and views are invalid.
|
|
115
|
+
page.addDatabase("child-empty-with-views", { datasources: {}, views: [] });
|
|
95
116
|
};
|
|
96
|
-
|
|
97
|
-
expect(
|
|
117
|
+
void invalidChildDeclarations;
|
|
118
|
+
expect(() =>
|
|
119
|
+
page.addDatabase("child-runtime-empty", { datasources: {}, views: [] } as never),
|
|
120
|
+
).toThrow();
|
|
121
|
+
|
|
122
|
+
const intents = finishMetadataRecording(recorder).intents;
|
|
123
|
+
expect(intents).toHaveLength(2);
|
|
124
|
+
expect(intents).toEqual(
|
|
125
|
+
expect.arrayContaining([
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
type: "database",
|
|
128
|
+
resourceId: "child-multi-database",
|
|
129
|
+
parent: { type: "resourceId", resourceId: "child-page" },
|
|
130
|
+
dataSources: [
|
|
131
|
+
{
|
|
132
|
+
resourceId: "child-issues-source",
|
|
133
|
+
name: "Issues",
|
|
134
|
+
properties: [
|
|
135
|
+
{ resourceId: "child-name", name: "Name", type: "title" },
|
|
136
|
+
{ resourceId: "child-issue-id", name: "Issue ID", type: "text" },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
]),
|
|
142
|
+
);
|
|
98
143
|
});
|
|
99
144
|
});
|
|
100
145
|
|
|
@@ -163,41 +208,49 @@ describe("buildApp", () => {
|
|
|
163
208
|
it("builds a sync manifest with a Notion-as-Code meetings database", async () => {
|
|
164
209
|
const { manifest } = await buildApp(example("sync"));
|
|
165
210
|
|
|
166
|
-
expect(manifest).toEqual(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
211
|
+
expect(manifest).toEqual(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
$schema: "notion:apps-manifest:v1",
|
|
214
|
+
sdkVersion: SDK_VERSION,
|
|
215
|
+
xldbs: [],
|
|
216
|
+
pacers: [],
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
expect(manifest.databases).toHaveLength(2);
|
|
220
|
+
expect(manifest.databases).toEqual(
|
|
221
|
+
expect.arrayContaining([
|
|
222
|
+
expect.objectContaining({
|
|
171
223
|
key: "issues",
|
|
172
|
-
config: {
|
|
224
|
+
config: expect.objectContaining({
|
|
173
225
|
initialTitle: "GitHub Issues",
|
|
174
|
-
schema: {
|
|
175
|
-
properties: {
|
|
226
|
+
schema: expect.objectContaining({
|
|
227
|
+
properties: expect.objectContaining({
|
|
176
228
|
Name: { type: "title" },
|
|
177
229
|
"GitHub ID": { type: "text" },
|
|
178
230
|
Closed: { type: "checkbox" },
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
{
|
|
231
|
+
}),
|
|
232
|
+
}),
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
expect.objectContaining({
|
|
184
236
|
key: "homebase-meetings-db-source",
|
|
185
|
-
config: {
|
|
237
|
+
config: expect.objectContaining({
|
|
186
238
|
initialTitle: "Meetings",
|
|
187
|
-
schema: {
|
|
188
|
-
properties: {
|
|
239
|
+
schema: expect.objectContaining({
|
|
240
|
+
properties: expect.objectContaining({
|
|
189
241
|
Name: { type: "title" },
|
|
190
242
|
"Meeting ID": { type: "text" },
|
|
191
243
|
"Meeting date": { type: "date" },
|
|
192
244
|
"Prep complete": { type: "checkbox" },
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
}),
|
|
248
|
+
}),
|
|
249
|
+
]),
|
|
250
|
+
);
|
|
251
|
+
expect(manifest.capabilities).toHaveLength(2);
|
|
252
|
+
expect(manifest.capabilities).toEqual(
|
|
253
|
+
expect.arrayContaining([
|
|
201
254
|
{
|
|
202
255
|
type: "sync",
|
|
203
256
|
key: "issues",
|
|
@@ -218,8 +271,8 @@ describe("buildApp", () => {
|
|
|
218
271
|
schedule: { type: "interval", intervalMs: 3_600_000 },
|
|
219
272
|
},
|
|
220
273
|
},
|
|
221
|
-
],
|
|
222
|
-
|
|
274
|
+
]),
|
|
275
|
+
);
|
|
223
276
|
});
|
|
224
277
|
|
|
225
278
|
it("emits a separate Notion-as-Code provisioning artifact", async () => {
|
|
@@ -285,6 +338,13 @@ describe("buildApp", () => {
|
|
|
285
338
|
resourceId: "__notion_apps_workspace__",
|
|
286
339
|
},
|
|
287
340
|
dataSources: [],
|
|
341
|
+
views: [
|
|
342
|
+
{
|
|
343
|
+
resourceId: "github-app-private-view",
|
|
344
|
+
type: "table",
|
|
345
|
+
dataSourceResourceId: "issues-source",
|
|
346
|
+
},
|
|
347
|
+
],
|
|
288
348
|
},
|
|
289
349
|
{
|
|
290
350
|
type: "database",
|
package/src/index.ts
CHANGED