@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/workflow.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { writeOutput } from "./output.js";
|
|
|
17
17
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
18
18
|
import { readRunMetadata, type RunMetadata } from "./runtime-metadata.js";
|
|
19
19
|
import { createWorkflowTriggers } from "./triggers.generated.js";
|
|
20
|
+
import { createWorkflowStepState, type WorkflowState } from "./workflow-state.js";
|
|
20
21
|
import type {
|
|
21
22
|
WorkflowEventMap,
|
|
22
23
|
WorkflowTrigger,
|
|
@@ -29,6 +30,45 @@ type HandlerOptions = {
|
|
|
29
30
|
concreteOutput?: true;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
type WorkflowWaitDurationValues = {
|
|
34
|
+
milliseconds?: number;
|
|
35
|
+
seconds?: number;
|
|
36
|
+
minutes?: number;
|
|
37
|
+
hours?: number;
|
|
38
|
+
days?: number;
|
|
39
|
+
weeks?: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type WorkflowWaitDuration = {
|
|
43
|
+
[Unit in keyof WorkflowWaitDurationValues]-?: Required<Pick<WorkflowWaitDurationValues, Unit>> &
|
|
44
|
+
WorkflowWaitDurationValues;
|
|
45
|
+
}[keyof WorkflowWaitDurationValues];
|
|
46
|
+
|
|
47
|
+
type WorkflowWaitKey = string | string[];
|
|
48
|
+
|
|
49
|
+
export type WorkflowWaitUntilOptions =
|
|
50
|
+
| {
|
|
51
|
+
at: Date;
|
|
52
|
+
key?: WorkflowWaitKey;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
after: WorkflowWaitDuration;
|
|
56
|
+
key?: WorkflowWaitKey;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type WorkflowWaitUntilResult = {
|
|
60
|
+
status: "waiting";
|
|
61
|
+
wait: {
|
|
62
|
+
type: "until";
|
|
63
|
+
name: string;
|
|
64
|
+
resumeAtMs: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type WorkflowHandlerResult = { status: "success" } | WorkflowWaitUntilResult;
|
|
69
|
+
|
|
70
|
+
const MAX_WORKFLOW_WAIT_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
71
|
+
|
|
32
72
|
export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
|
|
33
73
|
|
|
34
74
|
export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMap[T["type"]];
|
|
@@ -111,7 +151,7 @@ export type Workflow<
|
|
|
111
151
|
handler: (
|
|
112
152
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
113
153
|
options?: HandlerOptions,
|
|
114
|
-
) => Promise<
|
|
154
|
+
) => Promise<WorkflowHandlerResult | undefined>;
|
|
115
155
|
};
|
|
116
156
|
|
|
117
157
|
/**
|
|
@@ -181,11 +221,12 @@ export function workflow<
|
|
|
181
221
|
async handler(
|
|
182
222
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
183
223
|
options?: HandlerOptions,
|
|
184
|
-
): Promise<
|
|
224
|
+
): Promise<WorkflowHandlerResult | undefined> {
|
|
185
225
|
try {
|
|
186
226
|
event = await resolveRuntimeInput(event);
|
|
187
227
|
const runMetadata = readRunMetadata();
|
|
188
228
|
const baseContext = createCapabilityContext();
|
|
229
|
+
const step = createStep(runMetadata);
|
|
189
230
|
const capabilityContext: WorkflowContext<TConnections> = {
|
|
190
231
|
...baseContext,
|
|
191
232
|
connections: createWorkflowConnections(
|
|
@@ -193,7 +234,8 @@ export function workflow<
|
|
|
193
234
|
connectionDeclarations,
|
|
194
235
|
),
|
|
195
236
|
...runMetadata,
|
|
196
|
-
step
|
|
237
|
+
step,
|
|
238
|
+
wait: createWait(step),
|
|
197
239
|
};
|
|
198
240
|
await configuration.handler(event, capabilityContext);
|
|
199
241
|
|
|
@@ -203,6 +245,18 @@ export function workflow<
|
|
|
203
245
|
|
|
204
246
|
writeOutput({ _tag: "success", value: { status: "success" } });
|
|
205
247
|
} catch (err) {
|
|
248
|
+
if (err instanceof WorkflowWaitInterrupt) {
|
|
249
|
+
if (options?.concreteOutput) {
|
|
250
|
+
return err.result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
writeOutput({
|
|
254
|
+
_tag: "wait",
|
|
255
|
+
wait: err.result.wait,
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
206
260
|
const error = new ExecutionError(err);
|
|
207
261
|
|
|
208
262
|
if (!options?.concreteOutput) {
|
|
@@ -229,6 +283,13 @@ export type StepContext = {
|
|
|
229
283
|
* Use this to make side effects idempotent when the workflow is retried.
|
|
230
284
|
*/
|
|
231
285
|
id: string;
|
|
286
|
+
/**
|
|
287
|
+
* Durable key-value state shared by every run of this workflow.
|
|
288
|
+
*
|
|
289
|
+
* State is exposed on the step context because every operation participates
|
|
290
|
+
* in the active step's durability and replay boundary.
|
|
291
|
+
*/
|
|
292
|
+
state: WorkflowState;
|
|
232
293
|
};
|
|
233
294
|
|
|
234
295
|
type WorkflowStepResult<T> = T extends void ? null : T;
|
|
@@ -252,6 +313,14 @@ type WorkflowStep = {
|
|
|
252
313
|
): Promise<WorkflowStepResult<T>>;
|
|
253
314
|
};
|
|
254
315
|
|
|
316
|
+
type WorkflowWait = {
|
|
317
|
+
/**
|
|
318
|
+
* Suspend the workflow until a relative duration has elapsed or an absolute
|
|
319
|
+
* date has been reached.
|
|
320
|
+
*/
|
|
321
|
+
until(name: string, options: WorkflowWaitUntilOptions): Promise<void>;
|
|
322
|
+
};
|
|
323
|
+
|
|
255
324
|
/** Context passed to a workflow handler. */
|
|
256
325
|
export type WorkflowContext<
|
|
257
326
|
TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations,
|
|
@@ -279,6 +348,8 @@ export type WorkflowContext<
|
|
|
279
348
|
* ```
|
|
280
349
|
*/
|
|
281
350
|
step: WorkflowStep;
|
|
351
|
+
/** Suspend this workflow without consuming compute resources. */
|
|
352
|
+
wait: WorkflowWait;
|
|
282
353
|
};
|
|
283
354
|
|
|
284
355
|
type StepCompletionType = "success" | "failure";
|
|
@@ -314,7 +385,10 @@ function writeStepEvent(
|
|
|
314
385
|
);
|
|
315
386
|
}
|
|
316
387
|
|
|
317
|
-
function createStep(
|
|
388
|
+
function createStep(
|
|
389
|
+
metadata: Pick<RunMetadata, "runGroupId" | "workerId">,
|
|
390
|
+
): WorkflowContext["step"] {
|
|
391
|
+
const { runGroupId } = metadata;
|
|
318
392
|
const stepNameByKey = new Map<string, string>();
|
|
319
393
|
|
|
320
394
|
async function step<T>(
|
|
@@ -338,7 +412,9 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
338
412
|
}
|
|
339
413
|
stepNameByKey.set(key, name);
|
|
340
414
|
|
|
341
|
-
const
|
|
415
|
+
const id = `${runGroupId}:${key}`;
|
|
416
|
+
const stepState = createWorkflowStepState(metadata, id);
|
|
417
|
+
const context: StepContext = { id, state: stepState.state };
|
|
342
418
|
const event = { id: context.id, name, key };
|
|
343
419
|
const startedAt = performance.now();
|
|
344
420
|
|
|
@@ -367,7 +443,7 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
367
443
|
return checkpoint.value as WorkflowStepResult<T>;
|
|
368
444
|
}
|
|
369
445
|
|
|
370
|
-
const value = normalizeWorkflowStepResult(await fn(context));
|
|
446
|
+
const value = normalizeWorkflowStepResult(await stepState.run(() => fn(context)));
|
|
371
447
|
writeStepEvent("success", { ...event, value }, startedAt);
|
|
372
448
|
writeStepEvent(
|
|
373
449
|
"completed",
|
|
@@ -407,6 +483,104 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
407
483
|
return step;
|
|
408
484
|
}
|
|
409
485
|
|
|
486
|
+
class WorkflowWaitInterrupt extends Error {
|
|
487
|
+
constructor(readonly result: WorkflowWaitUntilResult) {
|
|
488
|
+
super(`Workflow is waiting until ${new Date(result.wait.resumeAtMs).toISOString()}`);
|
|
489
|
+
this.name = "WorkflowWaitInterrupt";
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function createWait(step: WorkflowStep): WorkflowWait {
|
|
494
|
+
return {
|
|
495
|
+
async until(name, options) {
|
|
496
|
+
const keyInput = options.key ?? name;
|
|
497
|
+
const keySegments = [
|
|
498
|
+
"wait",
|
|
499
|
+
"until",
|
|
500
|
+
...(typeof keyInput === "string" ? [keyInput] : keyInput),
|
|
501
|
+
];
|
|
502
|
+
const resumeAtMs = await step(name, { key: keySegments }, () =>
|
|
503
|
+
resolveWaitUntilMs(options),
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
if (Date.now() >= resumeAtMs) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
throw new WorkflowWaitInterrupt({
|
|
511
|
+
status: "waiting",
|
|
512
|
+
wait: {
|
|
513
|
+
type: "until",
|
|
514
|
+
name,
|
|
515
|
+
resumeAtMs,
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function resolveWaitUntilMs(options: WorkflowWaitUntilOptions): number {
|
|
523
|
+
const now = Date.now();
|
|
524
|
+
if ("at" in options) {
|
|
525
|
+
const resumeAtMs = options.at.getTime();
|
|
526
|
+
if (!Number.isFinite(resumeAtMs)) {
|
|
527
|
+
throw new Error("Workflow wait date must be valid.");
|
|
528
|
+
}
|
|
529
|
+
assertWaitFitsCheckpointRetention(resumeAtMs - now);
|
|
530
|
+
return resumeAtMs;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const durationMs = workflowWaitDurationMs(options.after);
|
|
534
|
+
assertWaitFitsCheckpointRetention(durationMs);
|
|
535
|
+
return now + durationMs;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function workflowWaitDurationMs(duration: WorkflowWaitDuration): number {
|
|
539
|
+
const entries = Object.entries(duration);
|
|
540
|
+
if (entries.length === 0) {
|
|
541
|
+
throw new Error("Workflow wait duration must specify at least one unit.");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let durationMs = 0;
|
|
545
|
+
for (const [unit, value] of entries) {
|
|
546
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
547
|
+
throw new Error("Workflow wait duration values must be positive finite integers.");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
durationMs += value * waitDurationUnitMultiplier(unit);
|
|
551
|
+
if (!Number.isSafeInteger(durationMs)) {
|
|
552
|
+
throw new Error("Workflow wait duration is too large.");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return durationMs;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function assertWaitFitsCheckpointRetention(durationMs: number): void {
|
|
560
|
+
if (durationMs >= MAX_WORKFLOW_WAIT_MS) {
|
|
561
|
+
throw new Error("Workflow waits must be shorter than 7 days.");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function waitDurationUnitMultiplier(unit: string): number {
|
|
566
|
+
switch (unit) {
|
|
567
|
+
case "milliseconds":
|
|
568
|
+
return 1;
|
|
569
|
+
case "seconds":
|
|
570
|
+
return 1_000;
|
|
571
|
+
case "minutes":
|
|
572
|
+
return 60_000;
|
|
573
|
+
case "hours":
|
|
574
|
+
return 3_600_000;
|
|
575
|
+
case "days":
|
|
576
|
+
return 86_400_000;
|
|
577
|
+
case "weeks":
|
|
578
|
+
return 604_800_000;
|
|
579
|
+
default:
|
|
580
|
+
throw new Error(`Unsupported workflow wait duration unit: ${unit}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
410
584
|
function createWorkflowStepKey(segments: readonly unknown[]): string {
|
|
411
585
|
if (segments.length === 0) {
|
|
412
586
|
throw new Error("Workflow step keys must contain at least one segment.");
|