@notionhq/apps 0.0.10 → 0.0.12
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/dist/cli/build.d.ts +2 -2
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +44 -2
- package/dist/cli/codegen.d.ts +5 -0
- package/dist/cli/codegen.d.ts.map +1 -1
- package/dist/cli/codegen.js +15 -1
- package/dist/cli/emit-manifest.d.ts +3 -0
- package/dist/cli/emit-manifest.d.ts.map +1 -1
- package/dist/cli/emit-manifest.js +12 -1
- package/dist/notion-as-code/custom-agent.d.ts +18 -0
- package/dist/notion-as-code/custom-agent.d.ts.map +1 -0
- package/dist/notion-as-code/custom-agent.js +13 -0
- package/dist/notion-as-code/database.d.ts +44 -0
- package/dist/notion-as-code/database.d.ts.map +1 -0
- package/dist/notion-as-code/database.js +68 -0
- package/dist/notion-as-code/date.d.ts +9 -0
- package/dist/notion-as-code/date.d.ts.map +1 -0
- package/dist/notion-as-code/date.js +69 -0
- package/dist/notion-as-code/handles.d.ts +32 -0
- package/dist/notion-as-code/handles.d.ts.map +1 -0
- package/dist/notion-as-code/handles.js +34 -0
- package/dist/notion-as-code/index.d.ts +30 -0
- package/dist/notion-as-code/index.d.ts.map +1 -0
- package/dist/notion-as-code/index.js +8 -0
- package/dist/notion-as-code/intents.d.ts +115 -0
- package/dist/notion-as-code/intents.d.ts.map +1 -0
- package/dist/notion-as-code/intents.js +6 -0
- package/dist/notion-as-code/page.d.ts +22 -0
- package/dist/notion-as-code/page.d.ts.map +1 -0
- package/dist/notion-as-code/page.js +31 -0
- package/dist/notion-as-code/provisioning.d.ts +22 -0
- package/dist/notion-as-code/provisioning.d.ts.map +1 -0
- package/dist/notion-as-code/provisioning.js +25 -0
- package/dist/notion-as-code/recorder.d.ts +10 -0
- package/dist/notion-as-code/recorder.d.ts.map +1 -0
- package/dist/notion-as-code/recorder.js +21 -0
- package/dist/notion-as-code/resource.d.ts +6 -0
- package/dist/notion-as-code/resource.d.ts.map +1 -0
- package/dist/notion-as-code/resource.js +19 -0
- package/dist/notion-as-code/schema.d.ts +63 -0
- package/dist/notion-as-code/schema.d.ts.map +1 -0
- package/dist/notion-as-code/schema.js +94 -0
- package/dist/notion-as-code/teamspace.d.ts +18 -0
- package/dist/notion-as-code/teamspace.d.ts.map +1 -0
- package/dist/notion-as-code/teamspace.js +30 -0
- package/dist/sync.d.ts +15 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +14 -3
- package/package.json +9 -1
- package/src/cli/build.test.ts +91 -1
- package/src/cli/build.ts +61 -6
- package/src/cli/codegen.ts +21 -0
- package/src/cli/discover.test.ts +6 -0
- package/src/cli/emit-manifest.ts +18 -0
- package/src/notion-as-code/custom-agent.ts +30 -0
- package/src/notion-as-code/database.ts +143 -0
- package/src/notion-as-code/date.ts +91 -0
- package/src/notion-as-code/handles.ts +36 -0
- package/src/notion-as-code/index.ts +16 -0
- package/src/notion-as-code/intents.ts +127 -0
- package/src/notion-as-code/page.ts +61 -0
- package/src/notion-as-code/provisioning.ts +50 -0
- package/src/notion-as-code/recorder.ts +26 -0
- package/src/notion-as-code/resource.ts +19 -0
- package/src/notion-as-code/schema.ts +149 -0
- package/src/notion-as-code/teamspace.ts +50 -0
- package/src/sync.ts +63 -5
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { InfraAsCodeIntent } from "./intents.js";
|
|
2
|
+
|
|
3
|
+
export const APPS_PROVISIONING_SCHEMA = "notion:apps-provisioning:v1";
|
|
4
|
+
|
|
5
|
+
/** Provisioning intents recorded while evaluating the build-only metadata bundle. */
|
|
6
|
+
export type RecordedProvisioning = {
|
|
7
|
+
intents: InfraAsCodeIntent[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Teamspaces are always created in the Apps workspace, so Apps supplies their parent. */
|
|
11
|
+
export type ProvisioningIntent =
|
|
12
|
+
| Omit<Extract<InfraAsCodeIntent, { type: "teamspace" }>, "parent">
|
|
13
|
+
| Exclude<InfraAsCodeIntent, { type: "teamspace" }>;
|
|
14
|
+
|
|
15
|
+
export type ProvisioningArtifact = {
|
|
16
|
+
$schema: typeof APPS_PROVISIONING_SCHEMA;
|
|
17
|
+
version: 1;
|
|
18
|
+
intents: readonly ProvisioningIntent[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Convert recorded declarations into the server-facing provisioning artifact. */
|
|
22
|
+
export function createProvisioningArtifact(
|
|
23
|
+
intents: readonly InfraAsCodeIntent[],
|
|
24
|
+
): ProvisioningArtifact {
|
|
25
|
+
const provisioningIntents: ProvisioningIntent[] = [];
|
|
26
|
+
for (const intent of intents) {
|
|
27
|
+
if (intent.type === "teamspace") {
|
|
28
|
+
const { parent: _parent, ...teamspace } = intent;
|
|
29
|
+
provisioningIntents.push(teamspace);
|
|
30
|
+
} else {
|
|
31
|
+
provisioningIntents.push(intent);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
$schema: APPS_PROVISIONING_SCHEMA,
|
|
37
|
+
version: 1,
|
|
38
|
+
intents: provisioningIntents,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Check that a metadata bundle exported the recorder output. */
|
|
43
|
+
export function isRecordedProvisioning(value: unknown): value is RecordedProvisioning {
|
|
44
|
+
return (
|
|
45
|
+
typeof value === "object" &&
|
|
46
|
+
value !== null &&
|
|
47
|
+
"intents" in value &&
|
|
48
|
+
Array.isArray(value.intents)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Records Notion-as-Code declarations while the build-only metadata bundle evaluates. */
|
|
2
|
+
import type { InfraAsCodeIntent } from "./intents.js";
|
|
3
|
+
import type { RecordedProvisioning } from "./provisioning.js";
|
|
4
|
+
|
|
5
|
+
let activeRecorder: RecordedProvisioning | undefined;
|
|
6
|
+
|
|
7
|
+
/** Start collecting declarations from the metadata bundle. */
|
|
8
|
+
export function startMetadataRecording(): RecordedProvisioning {
|
|
9
|
+
const recorder: RecordedProvisioning = { intents: [] };
|
|
10
|
+
activeRecorder = recorder;
|
|
11
|
+
return recorder;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Finish the active metadata collection and return its declarations. */
|
|
15
|
+
export function finishMetadataRecording(recorder: RecordedProvisioning): RecordedProvisioning {
|
|
16
|
+
if (activeRecorder !== recorder) {
|
|
17
|
+
throw new Error("Notion-as-Code metadata recorder was replaced before build completed");
|
|
18
|
+
}
|
|
19
|
+
activeRecorder = undefined;
|
|
20
|
+
return recorder;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Record one declaration when metadata collection is active. */
|
|
24
|
+
export function recordIntent(intent: InfraAsCodeIntent): void {
|
|
25
|
+
activeRecorder?.intents.push(intent);
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { APPS_RESERVED_RESOURCE_ID_PREFIX, type Parent } from "./intents.js";
|
|
2
|
+
|
|
3
|
+
/** Validate parent references accepted by Apps declarations. */
|
|
4
|
+
export function assertParent(parent: Parent): void {
|
|
5
|
+
if (parent.type !== "resourceId") {
|
|
6
|
+
throw new Error("Notion-as-Code parents must reference a resourceId");
|
|
7
|
+
}
|
|
8
|
+
assertUserResourceId(parent.resourceId);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Reject empty or runtime-reserved resource IDs in user declarations. */
|
|
12
|
+
export function assertUserResourceId(resourceId: string): void {
|
|
13
|
+
if (resourceId.length === 0) {
|
|
14
|
+
throw new Error("Notion-as-Code resourceId must be a non-empty string");
|
|
15
|
+
}
|
|
16
|
+
if (resourceId.startsWith(APPS_RESERVED_RESOURCE_ID_PREFIX)) {
|
|
17
|
+
throw new Error(`Resource ID "${resourceId}" uses the reserved Apps prefix`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// TODO: Converge on one schema. Until then, keep this translation layer aligned with Notion-as-Code.
|
|
2
|
+
import { createDatabase, type Database } from "../database.js";
|
|
3
|
+
import type { DatabaseSchema, PropertyConfiguration } from "../schema.js";
|
|
4
|
+
import type { SelectOption, StatusGroup } from "../types.js";
|
|
5
|
+
import type {
|
|
6
|
+
DataSourceDefinition,
|
|
7
|
+
NotionAsCodeProperty,
|
|
8
|
+
NotionAsCodeSelectOption,
|
|
9
|
+
NotionAsCodeStatusOption,
|
|
10
|
+
} from "./intents.js";
|
|
11
|
+
|
|
12
|
+
type AppsPropertyFor<Property extends NotionAsCodeProperty> = Property extends { type: "title" }
|
|
13
|
+
? { type: "title" }
|
|
14
|
+
: Property extends { type: "text" }
|
|
15
|
+
? { type: "text" }
|
|
16
|
+
: Property extends { type: "number" }
|
|
17
|
+
? { type: "number" }
|
|
18
|
+
: Property extends { type: "select" }
|
|
19
|
+
? { type: "select"; options: SelectOption[] }
|
|
20
|
+
: Property extends { type: "multi_select" }
|
|
21
|
+
? { type: "multi_select"; options: SelectOption[] }
|
|
22
|
+
: Property extends { type: "status" }
|
|
23
|
+
? { type: "status"; groups: StatusGroup[] }
|
|
24
|
+
: Property extends { type: "date" }
|
|
25
|
+
? { type: "date" }
|
|
26
|
+
: Property extends { type: "checkbox" }
|
|
27
|
+
? { type: "checkbox" }
|
|
28
|
+
: Property extends { type: "url" }
|
|
29
|
+
? { type: "url" }
|
|
30
|
+
: Property extends { type: "email" }
|
|
31
|
+
? { type: "email" }
|
|
32
|
+
: Property extends { type: "phone_number" }
|
|
33
|
+
? { type: "phone_number" }
|
|
34
|
+
: Property extends { type: "file" }
|
|
35
|
+
? { type: "file" }
|
|
36
|
+
: PropertyConfiguration;
|
|
37
|
+
|
|
38
|
+
export type AppsSchemaForProperties<Properties extends readonly NotionAsCodeProperty[]> = {
|
|
39
|
+
[Property in Properties[number] as Property["name"]]: AppsPropertyFor<Property>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Convert a Notion-as-Code data source into the Apps database used by sync handlers. */
|
|
43
|
+
export function createAppsDatabase(
|
|
44
|
+
databaseKey: string,
|
|
45
|
+
dataSource: DataSourceDefinition,
|
|
46
|
+
): Database {
|
|
47
|
+
const properties: DatabaseSchema = {};
|
|
48
|
+
const propertyResourceIds = new Set<string>();
|
|
49
|
+
let titleCount = 0;
|
|
50
|
+
|
|
51
|
+
for (const property of dataSource.properties) {
|
|
52
|
+
if (property.resourceId.length === 0) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Data source "${dataSource.resourceId}" has a property without a resourceId`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (propertyResourceIds.has(property.resourceId)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Data source "${dataSource.resourceId}" has duplicate property resourceId "${property.resourceId}"`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (properties[property.name] !== undefined) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Data source "${dataSource.resourceId}" has duplicate property name "${property.name}"`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
propertyResourceIds.add(property.resourceId);
|
|
68
|
+
if (property.type === "title") {
|
|
69
|
+
titleCount += 1;
|
|
70
|
+
}
|
|
71
|
+
properties[property.name] = toAppsProperty(dataSource.resourceId, property);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (titleCount !== 1) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Data source "${dataSource.resourceId}" must have exactly one title property to bind a sync`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return createDatabase(databaseKey, {
|
|
81
|
+
initialTitle: dataSource.name,
|
|
82
|
+
schema: properties,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Map Notion-as-Code properties to Apps properties; their schemas differ for selects and status groups. */
|
|
87
|
+
function toAppsProperty(
|
|
88
|
+
dataSourceResourceId: string,
|
|
89
|
+
property: NotionAsCodeProperty,
|
|
90
|
+
): PropertyConfiguration {
|
|
91
|
+
switch (property.type) {
|
|
92
|
+
case "title":
|
|
93
|
+
return { type: "title" };
|
|
94
|
+
case "text":
|
|
95
|
+
return { type: "text" };
|
|
96
|
+
case "number":
|
|
97
|
+
return { type: "number" };
|
|
98
|
+
case "select":
|
|
99
|
+
return { type: "select", options: (property.options ?? []).map(toAppsSelectOption) };
|
|
100
|
+
case "multi_select":
|
|
101
|
+
return {
|
|
102
|
+
type: "multi_select",
|
|
103
|
+
options: (property.options ?? []).map(toAppsSelectOption),
|
|
104
|
+
};
|
|
105
|
+
case "status":
|
|
106
|
+
return {
|
|
107
|
+
type: "status",
|
|
108
|
+
groups: [
|
|
109
|
+
{ name: "To-do", options: property.options.todo.map(toAppsStatusOption) },
|
|
110
|
+
{
|
|
111
|
+
name: "In progress",
|
|
112
|
+
options: property.options.inProgress.map(toAppsStatusOption),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "Complete",
|
|
116
|
+
options: property.options.complete.map(toAppsStatusOption),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
case "date":
|
|
121
|
+
return { type: "date" };
|
|
122
|
+
case "checkbox":
|
|
123
|
+
return { type: "checkbox" };
|
|
124
|
+
case "url":
|
|
125
|
+
return { type: "url" };
|
|
126
|
+
case "email":
|
|
127
|
+
return { type: "email" };
|
|
128
|
+
case "phone_number":
|
|
129
|
+
return { type: "phone_number" };
|
|
130
|
+
case "file":
|
|
131
|
+
return { type: "file" };
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Data source "${dataSourceResourceId}" property "${property.name}" uses unsupported sync type "${property.type}"`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function toAppsSelectOption(option: NotionAsCodeSelectOption): SelectOption {
|
|
140
|
+
return option.color === undefined
|
|
141
|
+
? { name: option.name }
|
|
142
|
+
: { name: option.name, color: option.color };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function toAppsStatusOption(option: NotionAsCodeStatusOption): SelectOption {
|
|
146
|
+
return option.color === undefined
|
|
147
|
+
? { name: option.name }
|
|
148
|
+
: { name: option.name, color: option.color };
|
|
149
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APPS_WORKSPACE_RESOURCE_ID,
|
|
3
|
+
type DataSourceDefinition,
|
|
4
|
+
type NotionAsCodeIcon,
|
|
5
|
+
type ResourceId,
|
|
6
|
+
type TeamspaceIntent,
|
|
7
|
+
} from "./intents.js";
|
|
8
|
+
import { database, type ChildDatabaseArgs, type DatabaseHandle } from "./database.js";
|
|
9
|
+
import { createPage, type ChildPageArgs, type PageHandle } from "./page.js";
|
|
10
|
+
import { recordIntent } from "./recorder.js";
|
|
11
|
+
import { assertUserResourceId } from "./resource.js";
|
|
12
|
+
|
|
13
|
+
export type TeamspaceArgs = {
|
|
14
|
+
resourceId: ResourceId;
|
|
15
|
+
name: string;
|
|
16
|
+
accessLevel: "default" | "open" | "closed" | "private";
|
|
17
|
+
icon?: NotionAsCodeIcon;
|
|
18
|
+
description?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type TeamspaceHandle = {
|
|
22
|
+
readonly resourceId: string;
|
|
23
|
+
addDatabase: <const DataSources extends readonly DataSourceDefinition[]>(
|
|
24
|
+
args: ChildDatabaseArgs<DataSources>,
|
|
25
|
+
) => DatabaseHandle<DataSources>;
|
|
26
|
+
addPage: (args: ChildPageArgs) => PageHandle;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Declare a teamspace that Apps automatically creates in its workspace. */
|
|
30
|
+
export function teamspace(args: TeamspaceArgs): TeamspaceHandle {
|
|
31
|
+
assertUserResourceId(args.resourceId);
|
|
32
|
+
recordIntent({
|
|
33
|
+
type: "teamspace",
|
|
34
|
+
...args,
|
|
35
|
+
parent: { type: "resourceId", resourceId: APPS_WORKSPACE_RESOURCE_ID },
|
|
36
|
+
} satisfies { type: "teamspace" } & TeamspaceIntent);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
resourceId: args.resourceId,
|
|
40
|
+
addDatabase(databaseArgs) {
|
|
41
|
+
return database({
|
|
42
|
+
...databaseArgs,
|
|
43
|
+
parent: { type: "resourceId", resourceId: args.resourceId },
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
addPage(pageArgs) {
|
|
47
|
+
return createPage(pageArgs, { type: "resourceId", resourceId: args.resourceId });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/sync.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { CapabilityContext } from "./context.js";
|
|
|
2
2
|
import { createCapabilityContext } from "./context.js";
|
|
3
3
|
import type { Database } from "./database.js";
|
|
4
4
|
import { ExecutionError, RateLimitError } from "./error.js";
|
|
5
|
+
import type { DataSourceHandle } from "./notion-as-code/database.js";
|
|
6
|
+
import type { NotionAsCodeProperty } from "./notion-as-code/intents.js";
|
|
7
|
+
import type { AppsSchemaForProperties } from "./notion-as-code/schema.js";
|
|
5
8
|
import { writeOutput } from "./output.js";
|
|
6
9
|
import { initPacerState, pacerStates, type PacerEntry } from "./pacer.js";
|
|
7
10
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
@@ -82,7 +85,25 @@ export type SyncConfiguration<
|
|
|
82
85
|
handler: (
|
|
83
86
|
state: State | undefined,
|
|
84
87
|
context: CapabilityContext,
|
|
85
|
-
) => Promise<SyncHandlerResult<S
|
|
88
|
+
) => Promise<SyncHandlerResult<NoInfer<S>, NoInfer<PrimaryKey>, State>>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Configuration for a sync backed by a Notion-as-Code data source. */
|
|
92
|
+
type DataSourceSyncConfiguration<
|
|
93
|
+
Properties extends readonly NotionAsCodeProperty[],
|
|
94
|
+
PrimaryKey extends KeyPropertyName<AppsSchemaForProperties<Properties>>,
|
|
95
|
+
State,
|
|
96
|
+
> = {
|
|
97
|
+
dataSource: DataSourceHandle<Properties>;
|
|
98
|
+
primaryKey: PrimaryKey;
|
|
99
|
+
mode?: SyncMode;
|
|
100
|
+
schedule?: Schedule;
|
|
101
|
+
handler: (
|
|
102
|
+
state: State | undefined,
|
|
103
|
+
context: CapabilityContext,
|
|
104
|
+
) => Promise<
|
|
105
|
+
SyncHandlerResult<NoInfer<AppsSchemaForProperties<Properties>>, NoInfer<PrimaryKey>, State>
|
|
106
|
+
>;
|
|
86
107
|
};
|
|
87
108
|
|
|
88
109
|
export type SyncRuntimeContext = {
|
|
@@ -98,6 +119,29 @@ type SyncDefinitionConfig = {
|
|
|
98
119
|
schedule?: NormalizedSchedule;
|
|
99
120
|
};
|
|
100
121
|
|
|
122
|
+
type SyncDatabaseConfiguration = { database: Database } | { dataSource: { database: Database } };
|
|
123
|
+
|
|
124
|
+
type RuntimeSyncChange =
|
|
125
|
+
| { type: "delete"; key: string }
|
|
126
|
+
| { type: "upsert"; key: string; properties: Record<string, unknown> };
|
|
127
|
+
|
|
128
|
+
type SyncDefinitionConfiguration<State> = SyncDatabaseConfiguration & {
|
|
129
|
+
primaryKey: string;
|
|
130
|
+
mode?: SyncMode;
|
|
131
|
+
schedule?: Schedule;
|
|
132
|
+
handler: (
|
|
133
|
+
state: State | undefined,
|
|
134
|
+
context: CapabilityContext,
|
|
135
|
+
) => Promise<{ changes: RuntimeSyncChange[]; hasMore?: boolean; nextState?: State }>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** Resolve a Notion-as-Code data source to the Apps database used by its sync. */
|
|
139
|
+
function resolveSyncDatabase(configuration: SyncDatabaseConfiguration): Database {
|
|
140
|
+
return "dataSource" in configuration
|
|
141
|
+
? configuration.dataSource.database
|
|
142
|
+
: configuration.database;
|
|
143
|
+
}
|
|
144
|
+
|
|
101
145
|
export type SyncRunResult = {
|
|
102
146
|
changes: unknown[];
|
|
103
147
|
hasMore: boolean;
|
|
@@ -111,16 +155,30 @@ export type Sync = {
|
|
|
111
155
|
handler: (context?: SyncRuntimeContext, options?: HandlerOptions) => Promise<SyncRunResult>;
|
|
112
156
|
};
|
|
113
157
|
|
|
114
|
-
/** Create a database sync. Default-export it from a file
|
|
158
|
+
/** Create a database sync. Default-export it from a sync file. */
|
|
115
159
|
export function createSync<
|
|
116
160
|
const S extends DatabaseSchema,
|
|
117
161
|
const PrimaryKey extends KeyPropertyName<S>,
|
|
118
162
|
State = unknown,
|
|
119
163
|
>(configuration: SyncConfiguration<S, PrimaryKey, State>): Sync {
|
|
164
|
+
return createSyncDefinition(configuration);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Create a sync backed by a Notion-as-Code data source. */
|
|
168
|
+
export function createDataSourceSync<
|
|
169
|
+
const Properties extends readonly NotionAsCodeProperty[],
|
|
170
|
+
const PrimaryKey extends KeyPropertyName<AppsSchemaForProperties<Properties>>,
|
|
171
|
+
State = unknown,
|
|
172
|
+
>(configuration: DataSourceSyncConfiguration<Properties, PrimaryKey, State>): Sync {
|
|
173
|
+
return createSyncDefinition(configuration);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createSyncDefinition<State>(configuration: SyncDefinitionConfiguration<State>): Sync {
|
|
120
177
|
const schedule =
|
|
121
178
|
configuration.schedule === undefined ? undefined : parseSchedule(configuration.schedule);
|
|
179
|
+
const database = resolveSyncDatabase(configuration);
|
|
122
180
|
const config: SyncDefinitionConfig = {
|
|
123
|
-
database
|
|
181
|
+
database,
|
|
124
182
|
primaryKeyProperty: configuration.primaryKey,
|
|
125
183
|
...(configuration.mode === undefined ? {} : { mode: configuration.mode }),
|
|
126
184
|
...(schedule === undefined ? {} : { schedule }),
|
|
@@ -139,7 +197,7 @@ export function createSync<
|
|
|
139
197
|
const handlerResult = await configuration.handler(state, createCapabilityContext());
|
|
140
198
|
const changes = handlerResult.changes.map((change) => {
|
|
141
199
|
if (change.type === "delete") {
|
|
142
|
-
return { ...change, targetDatabaseKey:
|
|
200
|
+
return { ...change, targetDatabaseKey: database.key };
|
|
143
201
|
}
|
|
144
202
|
return {
|
|
145
203
|
...change,
|
|
@@ -147,7 +205,7 @@ export function createSync<
|
|
|
147
205
|
...change.properties,
|
|
148
206
|
[configuration.primaryKey]: [[change.key]],
|
|
149
207
|
},
|
|
150
|
-
targetDatabaseKey:
|
|
208
|
+
targetDatabaseKey: database.key,
|
|
151
209
|
};
|
|
152
210
|
});
|
|
153
211
|
const result: SyncRunResult = {
|