@notionhq/workers 0.5.0 → 0.7.0
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/alpha/ajv-formats.d.js +0 -0
- package/dist/alpha/builder.d.ts +149 -0
- package/dist/alpha/builder.d.ts.map +1 -0
- package/dist/alpha/builder.js +260 -0
- package/dist/alpha/builder.test.d.ts +2 -0
- package/dist/alpha/builder.test.d.ts.map +1 -0
- package/dist/alpha/cli/build.d.ts +13 -0
- package/dist/alpha/cli/build.d.ts.map +1 -0
- package/dist/alpha/cli/build.js +36 -0
- package/dist/alpha/cli/build.test.d.ts +2 -0
- package/dist/alpha/cli/build.test.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.d.ts +10 -0
- package/dist/alpha/cli/codegen.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.js +41 -0
- package/dist/alpha/cli/codegen.test.d.ts +2 -0
- package/dist/alpha/cli/codegen.test.d.ts.map +1 -0
- package/dist/alpha/cli/discover.d.ts +35 -0
- package/dist/alpha/cli/discover.d.ts.map +1 -0
- package/dist/alpha/cli/discover.js +39 -0
- package/dist/alpha/cli/discover.test.d.ts +2 -0
- package/dist/alpha/cli/discover.test.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.d.ts +33 -0
- package/dist/alpha/cli/emit-manifest.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.js +266 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts +2 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts.map +1 -0
- package/dist/alpha/cli/index.d.ts +3 -0
- package/dist/alpha/cli/index.d.ts.map +1 -0
- package/dist/alpha/cli/index.js +36 -0
- package/dist/alpha/context.d.ts +8 -0
- package/dist/alpha/context.d.ts.map +1 -0
- package/dist/alpha/context.js +33 -0
- package/dist/alpha/database.d.ts +90 -0
- package/dist/alpha/database.d.ts.map +1 -0
- package/dist/alpha/database.js +9 -0
- package/dist/alpha/error.d.ts +41 -0
- package/dist/alpha/error.d.ts.map +1 -0
- package/dist/alpha/error.js +25 -0
- package/dist/alpha/icon-names.d.ts +6 -0
- package/dist/alpha/icon-names.d.ts.map +1 -0
- package/dist/alpha/icon-names.js +0 -0
- package/dist/alpha/json-schema.d.ts +117 -0
- package/dist/alpha/json-schema.d.ts.map +1 -0
- package/dist/alpha/json-schema.js +0 -0
- package/dist/alpha/manifest.d.ts +44 -0
- package/dist/alpha/manifest.d.ts.map +1 -0
- package/dist/alpha/manifest.js +0 -0
- package/dist/alpha/output.d.ts +7 -0
- package/dist/alpha/output.d.ts.map +1 -0
- package/dist/alpha/output.js +8 -0
- package/dist/alpha/pacer.d.ts +74 -0
- package/dist/alpha/pacer.d.ts.map +1 -0
- package/dist/alpha/pacer.js +66 -0
- package/dist/alpha/pacer.test.d.ts +2 -0
- package/dist/alpha/pacer.test.d.ts.map +1 -0
- package/dist/alpha/schedule.d.ts +6 -0
- package/dist/alpha/schedule.d.ts.map +1 -0
- package/dist/alpha/schedule.js +43 -0
- package/dist/alpha/schedule.test.d.ts +2 -0
- package/dist/alpha/schedule.test.d.ts.map +1 -0
- package/dist/alpha/schema-builder.d.ts +79 -0
- package/dist/alpha/schema-builder.d.ts.map +1 -0
- package/dist/alpha/schema-builder.js +135 -0
- package/dist/alpha/schema.d.ts +185 -0
- package/dist/alpha/schema.d.ts.map +1 -0
- package/dist/alpha/schema.js +115 -0
- package/dist/alpha/sync.d.ts +233 -0
- package/dist/alpha/sync.d.ts.map +1 -0
- package/dist/alpha/sync.js +70 -0
- package/dist/alpha/sync.test.d.ts +2 -0
- package/dist/alpha/sync.test.d.ts.map +1 -0
- package/dist/alpha/tool.d.ts +132 -0
- package/dist/alpha/tool.d.ts.map +1 -0
- package/dist/alpha/tool.js +156 -0
- package/dist/alpha/tool.test.d.ts +2 -0
- package/dist/alpha/tool.test.d.ts.map +1 -0
- package/dist/alpha/triggers.d.ts +365 -0
- package/dist/alpha/triggers.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.d.ts +246 -0
- package/dist/alpha/triggers.generated.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.js +239 -0
- package/dist/alpha/triggers.js +0 -0
- package/dist/alpha/types.d.ts +228 -0
- package/dist/alpha/types.d.ts.map +1 -0
- package/dist/alpha/types.js +0 -0
- package/dist/alpha/workflow.d.ts +70 -0
- package/dist/alpha/workflow.d.ts.map +1 -0
- package/dist/alpha/workflow.js +39 -0
- package/dist/alpha/workflow.test.d.ts +2 -0
- package/dist/alpha/workflow.test.d.ts.map +1 -0
- package/dist/block.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +2 -6
- package/dist/capabilities/ai_connector.d.ts.map +1 -1
- package/dist/capabilities/automation.d.ts.map +1 -1
- package/dist/capabilities/context.d.ts.map +1 -1
- package/dist/capabilities/oauth.d.ts.map +1 -1
- package/dist/capabilities/output.d.ts.map +1 -1
- package/dist/capabilities/output.js +2 -4
- package/dist/capabilities/stateful-capability.d.ts.map +1 -1
- package/dist/capabilities/stateful-capability.js +2 -6
- package/dist/capabilities/sync.d.ts.map +1 -1
- package/dist/capabilities/sync.js +2 -8
- package/dist/capabilities/tool.d.ts.map +1 -1
- package/dist/capabilities/tool.js +6 -20
- package/dist/capabilities/webhook.d.ts.map +1 -1
- package/dist/capabilities/workflow.d.ts +2 -2
- package/dist/capabilities/workflow.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -7
- package/dist/json-schema.d.ts.map +1 -1
- package/dist/schema-builder.d.ts.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1 -4
- package/package.json +74 -14
- package/src/alpha/ajv-formats.d.ts +7 -0
- package/src/alpha/builder.test.ts +197 -0
- package/src/alpha/builder.ts +338 -0
- package/src/alpha/cli/build.test.ts +240 -0
- package/src/alpha/cli/build.ts +57 -0
- package/src/alpha/cli/codegen.test.ts +31 -0
- package/src/alpha/cli/codegen.ts +51 -0
- package/src/alpha/cli/discover.test.ts +43 -0
- package/src/alpha/cli/discover.ts +81 -0
- package/src/alpha/cli/emit-manifest.test.ts +45 -0
- package/src/alpha/cli/emit-manifest.ts +409 -0
- package/src/alpha/cli/index.ts +41 -0
- package/src/alpha/context.ts +61 -0
- package/src/alpha/database.ts +102 -0
- package/src/alpha/error.ts +49 -0
- package/src/alpha/icon-names.ts +890 -0
- package/src/alpha/json-schema.ts +180 -0
- package/src/alpha/manifest.ts +47 -0
- package/src/alpha/output.ts +8 -0
- package/src/alpha/pacer.test.ts +81 -0
- package/src/alpha/pacer.ts +181 -0
- package/src/alpha/schedule.test.ts +31 -0
- package/src/alpha/schedule.ts +53 -0
- package/src/alpha/schema-builder.ts +193 -0
- package/src/alpha/schema.ts +222 -0
- package/src/alpha/sync.test.ts +191 -0
- package/src/alpha/sync.ts +340 -0
- package/src/alpha/tool.test.ts +116 -0
- package/src/alpha/tool.ts +319 -0
- package/src/alpha/triggers.generated.ts +489 -0
- package/src/alpha/triggers.ts +500 -0
- package/src/alpha/types.ts +284 -0
- package/src/alpha/workflow.test.ts +84 -0
- package/src/alpha/workflow.ts +131 -0
- package/src/block.ts +1 -5
- package/src/builder.test.ts +28 -60
- package/src/builder.ts +3 -10
- package/src/capabilities/ai_connector.test.ts +9 -32
- package/src/capabilities/ai_connector.ts +5 -17
- package/src/capabilities/automation.test.ts +3 -12
- package/src/capabilities/automation.ts +3 -11
- package/src/capabilities/context.ts +3 -6
- package/src/capabilities/oauth.test.ts +1 -0
- package/src/capabilities/oauth.ts +1 -3
- package/src/capabilities/output.ts +1 -3
- package/src/capabilities/stateful-capability.test.ts +1 -0
- package/src/capabilities/stateful-capability.ts +2 -6
- package/src/capabilities/sync.test.ts +15 -45
- package/src/capabilities/sync.ts +10 -40
- package/src/capabilities/tool.test.ts +106 -146
- package/src/capabilities/tool.ts +17 -39
- package/src/capabilities/webhook.ts +3 -10
- package/src/capabilities/workflow.test.ts +10 -31
- package/src/capabilities/workflow.ts +5 -12
- package/src/index.ts +2 -12
- package/src/json-schema.test.ts +2 -4
- package/src/json-schema.ts +7 -6
- package/src/pacer.test.ts +1 -3
- package/src/pacer_internal.ts +1 -5
- package/src/schema-builder.test.ts +10 -35
- package/src/schema-builder.ts +5 -16
- package/src/schema.ts +2 -10
- package/src/types.ts +1 -7
- package/src/worker.ts +8 -34
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod-like builder for JSONSchema objects.
|
|
3
|
+
*
|
|
4
|
+
* Guarantees output always complies with the model provider constraints
|
|
5
|
+
* enforced by `json-schema.ts` — all object properties in `required`,
|
|
6
|
+
* `additionalProperties: false`, no forbidden composition keywords, etc.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { j } from "@notionhq/workers/alpha/schema-builder";
|
|
11
|
+
*
|
|
12
|
+
* const schema = j.object({
|
|
13
|
+
* foo: j.enum("a", "b", "c").describe("The foo field"),
|
|
14
|
+
* bar: j.string(),
|
|
15
|
+
* count: j.number(),
|
|
16
|
+
* tags: j.array(j.string()),
|
|
17
|
+
* nickname: j.string().nullable(),
|
|
18
|
+
* createdAt: j.datetime(),
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { AnyJSONSchema, JSONSchema } from "./json-schema.js";
|
|
24
|
+
|
|
25
|
+
const SCHEMA: unique symbol = Symbol("schema");
|
|
26
|
+
|
|
27
|
+
export interface SchemaBuilder<T> {
|
|
28
|
+
readonly [SCHEMA]: AnyJSONSchema;
|
|
29
|
+
/** Set the JSONSchema description field. Chainable. */
|
|
30
|
+
describe(text: string): SchemaBuilder<T>;
|
|
31
|
+
/** Wrap in anyOf with null. Chainable. */
|
|
32
|
+
nullable(): SchemaBuilder<T | null>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Extracts `T` from `SchemaBuilder<T>` (type-level only). */
|
|
36
|
+
export type Infer<S> = S extends SchemaBuilder<infer T> ? T : never;
|
|
37
|
+
|
|
38
|
+
/** Extract the plain JSONSchema from a builder. */
|
|
39
|
+
export function getSchema<T>(builder: SchemaBuilder<T>): JSONSchema<T> {
|
|
40
|
+
return builder[SCHEMA] as JSONSchema<T>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function makeBuilder<T>(schema: AnyJSONSchema): SchemaBuilder<T> {
|
|
44
|
+
return {
|
|
45
|
+
[SCHEMA]: schema,
|
|
46
|
+
describe(text: string) {
|
|
47
|
+
return makeBuilder<T>({ ...schema, description: text });
|
|
48
|
+
},
|
|
49
|
+
nullable() {
|
|
50
|
+
const { description: desc, ...rest } = schema as unknown as Record<string, unknown>;
|
|
51
|
+
const inner = desc !== undefined ? (rest as unknown as AnyJSONSchema) : schema;
|
|
52
|
+
const wrapper: Record<string, unknown> = {
|
|
53
|
+
anyOf: [inner, { type: "null" }],
|
|
54
|
+
};
|
|
55
|
+
if (desc !== undefined) wrapper.description = desc;
|
|
56
|
+
return makeBuilder<T | null>(wrapper as unknown as AnyJSONSchema);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---- Primitives ----
|
|
62
|
+
|
|
63
|
+
export function string(): SchemaBuilder<string> {
|
|
64
|
+
return makeBuilder<string>({ type: "string" });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function number(): SchemaBuilder<number> {
|
|
68
|
+
return makeBuilder<number>({ type: "number" });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function integer(): SchemaBuilder<number> {
|
|
72
|
+
return makeBuilder<number>({ type: "integer" });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function boolean(): SchemaBuilder<boolean> {
|
|
76
|
+
return makeBuilder<boolean>({ type: "boolean" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---- Enum ----
|
|
80
|
+
|
|
81
|
+
function enumBuilder<T extends string>(...values: readonly T[]): SchemaBuilder<T>;
|
|
82
|
+
function enumBuilder<T extends number>(...values: readonly T[]): SchemaBuilder<T>;
|
|
83
|
+
function enumBuilder(...values: readonly (string | number)[]): SchemaBuilder<unknown> {
|
|
84
|
+
const type = typeof values[0] === "string" ? "string" : "number";
|
|
85
|
+
return makeBuilder({ type, enum: values } as AnyJSONSchema);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { enumBuilder as enum };
|
|
89
|
+
|
|
90
|
+
// ---- String format shorthands ----
|
|
91
|
+
|
|
92
|
+
export function datetime(): SchemaBuilder<string> {
|
|
93
|
+
return makeBuilder<string>({ type: "string", format: "date-time" });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function date(): SchemaBuilder<string> {
|
|
97
|
+
return makeBuilder<string>({ type: "string", format: "date" });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function time(): SchemaBuilder<string> {
|
|
101
|
+
return makeBuilder<string>({ type: "string", format: "time" });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function duration(): SchemaBuilder<string> {
|
|
105
|
+
return makeBuilder<string>({ type: "string", format: "duration" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function email(): SchemaBuilder<string> {
|
|
109
|
+
return makeBuilder<string>({ type: "string", format: "email" });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function hostname(): SchemaBuilder<string> {
|
|
113
|
+
return makeBuilder<string>({ type: "string", format: "hostname" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function ipv4(): SchemaBuilder<string> {
|
|
117
|
+
return makeBuilder<string>({ type: "string", format: "ipv4" });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function ipv6(): SchemaBuilder<string> {
|
|
121
|
+
return makeBuilder<string>({ type: "string", format: "ipv6" });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function uuid(): SchemaBuilder<string> {
|
|
125
|
+
return makeBuilder<string>({ type: "string", format: "uuid" });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---- Composites ----
|
|
129
|
+
|
|
130
|
+
export function array<T>(
|
|
131
|
+
items: SchemaBuilder<T>,
|
|
132
|
+
options?: { minItems?: 0 | 1 },
|
|
133
|
+
): SchemaBuilder<T[]> {
|
|
134
|
+
const schema: Record<string, unknown> = {
|
|
135
|
+
type: "array",
|
|
136
|
+
items: getSchema(items),
|
|
137
|
+
};
|
|
138
|
+
if (options?.minItems !== undefined) schema.minItems = options.minItems;
|
|
139
|
+
return makeBuilder<T[]>(schema as unknown as AnyJSONSchema);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function object<P extends Record<string, SchemaBuilder<unknown>>>(
|
|
143
|
+
properties: P,
|
|
144
|
+
): SchemaBuilder<{ [K in keyof P]: Infer<P[K]> }> {
|
|
145
|
+
const schemaProps: Record<string, AnyJSONSchema> = {};
|
|
146
|
+
const keys = Object.keys(properties);
|
|
147
|
+
for (const key of keys) {
|
|
148
|
+
schemaProps[key] = getSchema(properties[key]!);
|
|
149
|
+
}
|
|
150
|
+
return makeBuilder({
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: schemaProps,
|
|
153
|
+
required: keys,
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
} as AnyJSONSchema);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function anyOf<S extends SchemaBuilder<unknown>[]>(
|
|
159
|
+
...schemas: S
|
|
160
|
+
): SchemaBuilder<Infer<S[number]>> {
|
|
161
|
+
return makeBuilder({
|
|
162
|
+
anyOf: schemas.map(getSchema),
|
|
163
|
+
} as AnyJSONSchema);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---- Reference ----
|
|
167
|
+
|
|
168
|
+
export function ref(path: string): SchemaBuilder<unknown> {
|
|
169
|
+
return makeBuilder({ $ref: path });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---- Namespace export ----
|
|
173
|
+
|
|
174
|
+
export const j = {
|
|
175
|
+
string,
|
|
176
|
+
number,
|
|
177
|
+
integer,
|
|
178
|
+
boolean,
|
|
179
|
+
enum: enumBuilder,
|
|
180
|
+
datetime,
|
|
181
|
+
date,
|
|
182
|
+
time,
|
|
183
|
+
duration,
|
|
184
|
+
email,
|
|
185
|
+
hostname,
|
|
186
|
+
ipv4,
|
|
187
|
+
ipv6,
|
|
188
|
+
uuid,
|
|
189
|
+
array,
|
|
190
|
+
object,
|
|
191
|
+
anyOf,
|
|
192
|
+
ref,
|
|
193
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { Database } from "./database.js";
|
|
2
|
+
import type { DateFormat, Icon, NumberFormat, SelectOption, StatusGroup } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A schema defining the structure of a single property in a data source.
|
|
6
|
+
*/
|
|
7
|
+
export type PropertyConfiguration =
|
|
8
|
+
| { type: "title" }
|
|
9
|
+
| { type: "text" }
|
|
10
|
+
| { type: "url" }
|
|
11
|
+
| { type: "email" }
|
|
12
|
+
| { type: "phone_number" }
|
|
13
|
+
| { type: "checkbox" }
|
|
14
|
+
| { type: "file" }
|
|
15
|
+
| {
|
|
16
|
+
type: "number";
|
|
17
|
+
format?: NumberFormat;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
type: "date";
|
|
21
|
+
date_format?: DateFormat;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
type: "select";
|
|
25
|
+
options: SelectOption[];
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
type: "multi_select";
|
|
29
|
+
options: SelectOption[];
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: "status";
|
|
33
|
+
groups: StatusGroup[];
|
|
34
|
+
}
|
|
35
|
+
| { type: "people" }
|
|
36
|
+
| { type: "place" }
|
|
37
|
+
| {
|
|
38
|
+
type: "relation";
|
|
39
|
+
/**
|
|
40
|
+
* The related database, referenced by identity. It must be declared
|
|
41
|
+
* under `src/databases/`; the build resolves the reference to the
|
|
42
|
+
* database's key when emitting the manifest.
|
|
43
|
+
*/
|
|
44
|
+
database: Database;
|
|
45
|
+
config: { twoWay: false } | { twoWay: true; relatedPropertyName: string };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The property definitions for a database, keyed by property name.
|
|
50
|
+
*/
|
|
51
|
+
export type PropertySchema = Record<string, PropertyConfiguration>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A database schema.
|
|
55
|
+
*/
|
|
56
|
+
export type Schema<S extends PropertySchema = PropertySchema> = {
|
|
57
|
+
/**
|
|
58
|
+
* Optional icon to use as the icon for the database page.
|
|
59
|
+
* If not provided, defaults to 📋.
|
|
60
|
+
*/
|
|
61
|
+
databaseIcon?: Icon;
|
|
62
|
+
properties: S;
|
|
63
|
+
/**
|
|
64
|
+
* Optional configuration for sub-item relations.
|
|
65
|
+
* If provided, the parent and child properties must be part of a two-way self-relation.
|
|
66
|
+
*/
|
|
67
|
+
subItems?: {
|
|
68
|
+
parentPropertyName: string;
|
|
69
|
+
childPropertyName: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Builders for property definitions in a database schema.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { Schema } from "@notionhq/workers/alpha/schema";
|
|
79
|
+
*
|
|
80
|
+
* const schema = {
|
|
81
|
+
* properties: {
|
|
82
|
+
* "Task Name": Schema.title(),
|
|
83
|
+
* "Task ID": Schema.richText(),
|
|
84
|
+
* Status: Schema.select([
|
|
85
|
+
* { name: "Open", color: "default" },
|
|
86
|
+
* { name: "Done", color: "green" },
|
|
87
|
+
* ]),
|
|
88
|
+
* },
|
|
89
|
+
* };
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export const Schema = {
|
|
93
|
+
/**
|
|
94
|
+
* Creates a title property definition.
|
|
95
|
+
*/
|
|
96
|
+
title(): PropertyConfiguration {
|
|
97
|
+
return { type: "title" };
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a rich text property definition.
|
|
102
|
+
*/
|
|
103
|
+
richText(): PropertyConfiguration {
|
|
104
|
+
return { type: "text" };
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a URL property definition.
|
|
109
|
+
*/
|
|
110
|
+
url(): PropertyConfiguration {
|
|
111
|
+
return { type: "url" };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Creates an email property definition.
|
|
116
|
+
*/
|
|
117
|
+
email(): PropertyConfiguration {
|
|
118
|
+
return { type: "email" };
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Creates a phone number property definition.
|
|
123
|
+
*/
|
|
124
|
+
phoneNumber(): PropertyConfiguration {
|
|
125
|
+
return { type: "phone_number" };
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a checkbox property definition.
|
|
130
|
+
*/
|
|
131
|
+
checkbox(): PropertyConfiguration {
|
|
132
|
+
return { type: "checkbox" };
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates a file property definition.
|
|
137
|
+
*/
|
|
138
|
+
file(): PropertyConfiguration {
|
|
139
|
+
return { type: "file" };
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates a number property definition with optional formatting.
|
|
144
|
+
*/
|
|
145
|
+
number(format?: NumberFormat): PropertyConfiguration {
|
|
146
|
+
return format ? { type: "number", format } : { type: "number" };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Creates a date property definition with optional formatting.
|
|
151
|
+
*/
|
|
152
|
+
date(date_format?: DateFormat): PropertyConfiguration {
|
|
153
|
+
return date_format ? { type: "date", date_format } : { type: "date" };
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Creates a select property definition with predefined options.
|
|
158
|
+
*/
|
|
159
|
+
select(options: SelectOption[]): PropertyConfiguration {
|
|
160
|
+
return { type: "select", options };
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Creates a multi-select property definition with predefined options.
|
|
165
|
+
*/
|
|
166
|
+
multiSelect(options: SelectOption[]): PropertyConfiguration {
|
|
167
|
+
return { type: "multi_select", options };
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Creates a status property definition with groups.
|
|
172
|
+
*/
|
|
173
|
+
status(config: { groups: StatusGroup[] }): PropertyConfiguration {
|
|
174
|
+
return { type: "status", groups: config.groups };
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates a people property definition.
|
|
179
|
+
*/
|
|
180
|
+
people(): PropertyConfiguration {
|
|
181
|
+
return { type: "people" };
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Creates a place property definition for storing geographic locations.
|
|
186
|
+
*/
|
|
187
|
+
place(): PropertyConfiguration {
|
|
188
|
+
return { type: "place" };
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Creates a relation property definition that references another database.
|
|
193
|
+
* The related database must be declared under `src/databases/` and is
|
|
194
|
+
* referenced by importing it:
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* // src/databases/tasks.ts
|
|
199
|
+
* import projects from "./projects.js";
|
|
200
|
+
*
|
|
201
|
+
* export default createDatabase({
|
|
202
|
+
* type: "managed",
|
|
203
|
+
* primaryKey: "Task ID",
|
|
204
|
+
* schema: {
|
|
205
|
+
* properties: {
|
|
206
|
+
* Title: Schema.title(),
|
|
207
|
+
* "Task ID": Schema.richText(),
|
|
208
|
+
* Project: Schema.relation(projects),
|
|
209
|
+
* },
|
|
210
|
+
* },
|
|
211
|
+
* });
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
relation(
|
|
215
|
+
// `Database<any>` so a database with any concrete schema is accepted;
|
|
216
|
+
// `keyof S` in the config makes `Database` invariant in its schema.
|
|
217
|
+
database: Database<any>,
|
|
218
|
+
config?: { twoWay: false } | { twoWay: true; relatedPropertyName: string },
|
|
219
|
+
): PropertyConfiguration {
|
|
220
|
+
return { type: "relation", database, config: config ?? { twoWay: false } };
|
|
221
|
+
},
|
|
222
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createDatabase } from "./database.js";
|
|
4
|
+
import { ExecutionError, RateLimitError } from "./error.js";
|
|
5
|
+
import { createPacer } from "./pacer.js";
|
|
6
|
+
import { createSync } from "./sync.js";
|
|
7
|
+
|
|
8
|
+
const database = createDatabase("tasks", {
|
|
9
|
+
type: "managed",
|
|
10
|
+
primaryKey: "ID",
|
|
11
|
+
schema: { properties: { ID: { type: "text" } } },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let stdoutSpy: Mock<typeof process.stdout.write>;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
stdoutSpy.mockRestore();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("createSync", () => {
|
|
25
|
+
it("returns a tagged capability with normalized config", () => {
|
|
26
|
+
const sync = createSync({
|
|
27
|
+
database,
|
|
28
|
+
mode: "replace",
|
|
29
|
+
schedule: "1h",
|
|
30
|
+
handler: async () => ({ changes: [] }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(sync._tag).toBe("sync");
|
|
34
|
+
expect(sync.config).toEqual({
|
|
35
|
+
database,
|
|
36
|
+
mode: "replace",
|
|
37
|
+
schedule: { type: "interval", intervalMs: 60 * 60 * 1000 },
|
|
38
|
+
});
|
|
39
|
+
expect(sync.config.database).toBe(database);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("rejects invalid schedules at creation time", () => {
|
|
43
|
+
expect(() =>
|
|
44
|
+
createSync({
|
|
45
|
+
database,
|
|
46
|
+
schedule: "30d",
|
|
47
|
+
handler: async () => ({ changes: [] }),
|
|
48
|
+
}),
|
|
49
|
+
).toThrow(/at most 7 days/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("sync handler", () => {
|
|
54
|
+
it("writes a success envelope and returns the run result", async () => {
|
|
55
|
+
const sync = createSync({
|
|
56
|
+
database,
|
|
57
|
+
handler: async () => ({
|
|
58
|
+
changes: [{ type: "upsert", key: "1", properties: { ID: [["1"]] } }],
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = await sync.handler();
|
|
63
|
+
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
changes: [
|
|
66
|
+
{ type: "upsert", key: "1", properties: { ID: [["1"]] }, targetDatabaseKey: "tasks" },
|
|
67
|
+
],
|
|
68
|
+
hasMore: false,
|
|
69
|
+
nextUserContext: undefined,
|
|
70
|
+
nextPacerStates: {},
|
|
71
|
+
});
|
|
72
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
73
|
+
`\n<__notion_output__>{"_tag":"success","value":{"changes":[{"type":"upsert","key":"1","properties":{"ID":[["1"]]},"targetDatabaseKey":"tasks"}],"hasMore":false,"nextPacerStates":{}}}</__notion_output__>\n`,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("passes state (or legacy userContext) and the context to the user handler", async () => {
|
|
78
|
+
const handler = vi.fn(async (_state: unknown, _context: { notion: unknown }) => ({
|
|
79
|
+
changes: [],
|
|
80
|
+
}));
|
|
81
|
+
const sync = createSync({ database, handler });
|
|
82
|
+
|
|
83
|
+
await sync.handler({ state: { cursor: "abc" } });
|
|
84
|
+
expect(handler).toHaveBeenLastCalledWith({ cursor: "abc" }, expect.anything());
|
|
85
|
+
|
|
86
|
+
await sync.handler({ userContext: { cursor: "legacy" } });
|
|
87
|
+
expect(handler).toHaveBeenLastCalledWith({ cursor: "legacy" }, expect.anything());
|
|
88
|
+
|
|
89
|
+
// The context carries the Notion client.
|
|
90
|
+
expect(handler.mock.lastCall?.[1]?.notion).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("threads nextState and hasMore through to the run result", async () => {
|
|
94
|
+
const sync = createSync({
|
|
95
|
+
database,
|
|
96
|
+
handler: async () => ({ changes: [], hasMore: true, nextState: { cursor: "next" } }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = await sync.handler(undefined, { concreteOutput: true });
|
|
100
|
+
expect(result.hasMore).toBe(true);
|
|
101
|
+
expect(result.nextUserContext).toEqual({ cursor: "next" });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("types pagination state end to end", async () => {
|
|
105
|
+
const sync = createSync({
|
|
106
|
+
database,
|
|
107
|
+
// State is inferred as { cursor: string } from the handler.
|
|
108
|
+
handler: async (state: { cursor: string } | undefined) => ({
|
|
109
|
+
changes: [],
|
|
110
|
+
hasMore: state === undefined,
|
|
111
|
+
nextState: { cursor: `${state?.cursor ?? ""}x` },
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await sync.handler({ state: { cursor: "a" } }, { concreteOutput: true });
|
|
116
|
+
expect(result.nextUserContext).toEqual({ cursor: "ax" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("threads pacer state from the runtime context to nextPacerStates", async () => {
|
|
120
|
+
const pacer = createPacer("sync-test-pacer", { allowedRequests: 10, intervalMs: 1000 });
|
|
121
|
+
const sync = createSync({
|
|
122
|
+
database,
|
|
123
|
+
handler: async () => {
|
|
124
|
+
await pacer.wait();
|
|
125
|
+
return { changes: [] };
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const result = await sync.handler(
|
|
130
|
+
{ pacers: { [pacer.name]: { lastScheduledAtMs: 0, allowedRequests: 5, intervalMs: 500 } } },
|
|
131
|
+
{ concreteOutput: true },
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// The run reports back the apportioned entry with an updated schedule.
|
|
135
|
+
expect(result.nextPacerStates[pacer.name]).toMatchObject({
|
|
136
|
+
allowedRequests: 5,
|
|
137
|
+
intervalMs: 500,
|
|
138
|
+
});
|
|
139
|
+
expect(result.nextPacerStates[pacer.name]?.lastScheduledAtMs).toBeGreaterThan(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("skips the envelope under concreteOutput", async () => {
|
|
143
|
+
const sync = createSync({
|
|
144
|
+
database,
|
|
145
|
+
handler: async () => ({ changes: [] }),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await sync.handler(undefined, { concreteOutput: true });
|
|
149
|
+
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("writes a rate_limit envelope and rethrows RateLimitError", async () => {
|
|
153
|
+
const sync = createSync({
|
|
154
|
+
database,
|
|
155
|
+
handler: async () => {
|
|
156
|
+
throw new RateLimitError({ retryAfter: 30 });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await expect(sync.handler()).rejects.toBeInstanceOf(RateLimitError);
|
|
161
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
162
|
+
`\n<__notion_output__>{"_tag":"error","error":{"_tag":"rate_limit","name":"RateLimitError","message":"Error during worker execution: Rate limited by external API","retryAfter":30}}</__notion_output__>\n`,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("writes a generic error envelope and throws ExecutionError", async () => {
|
|
167
|
+
const sync = createSync({
|
|
168
|
+
database,
|
|
169
|
+
handler: async () => {
|
|
170
|
+
throw new Error("boom");
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await expect(sync.handler()).rejects.toBeInstanceOf(ExecutionError);
|
|
175
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
176
|
+
`\n<__notion_output__>{"_tag":"error","error":{"_tag":"generic","name":"ExecutionError","message":"Error during worker execution: Error: boom"}}</__notion_output__>\n`,
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("createDatabase", () => {
|
|
182
|
+
it("rejects empty names", () => {
|
|
183
|
+
expect(() =>
|
|
184
|
+
createDatabase("", {
|
|
185
|
+
type: "managed",
|
|
186
|
+
primaryKey: "ID",
|
|
187
|
+
schema: { properties: { ID: { type: "text" } } },
|
|
188
|
+
}),
|
|
189
|
+
).toThrow(/non-empty string/);
|
|
190
|
+
});
|
|
191
|
+
});
|