@notionhq/apps 0.0.18 → 0.0.20
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/README.md +22 -1
- 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 +1 -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 +2 -0
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +3 -0
- package/dist/notion-as-code/date.d.ts +3 -2
- package/dist/notion-as-code/date.d.ts.map +1 -1
- package/dist/notion-as-code/handles.d.ts +8 -11
- package/dist/notion-as-code/handles.d.ts.map +1 -1
- package/dist/notion-as-code/handles.js +8 -7
- package/dist/notion-as-code/index.d.ts +8 -11
- package/dist/notion-as-code/index.d.ts.map +1 -1
- package/dist/notion-as-code/page.d.ts +50 -1
- 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/page.test.d.ts +2 -0
- package/dist/notion-as-code/page.test.d.ts.map +1 -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.d.ts +17 -6
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +8 -1
- package/docs/BUILD.md +38 -4
- package/package.json +1 -1
- package/skills/notion-as-code/SKILL.md +16 -3
- package/skills/workflow/SKILL.md +53 -16
- package/src/cli/build.test.ts +24 -0
- package/src/cli/build.ts +13 -8
- package/src/cli/emit-manifest.ts +107 -0
- package/src/index.ts +20 -1
- package/src/notion-as-code/custom-agent.ts +2 -1
- package/src/notion-as-code/database.ts +5 -0
- package/src/notion-as-code/date.ts +4 -2
- package/src/notion-as-code/handles.ts +16 -12
- package/src/notion-as-code/index.ts +10 -1
- package/src/notion-as-code/page.test.ts +35 -0
- package/src/notion-as-code/page.ts +62 -2
- package/src/workflow-access-types.test.ts +69 -0
- package/src/workflow-access.ts +299 -0
- package/src/workflow.test.ts +112 -0
- package/src/workflow.ts +50 -7
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const ACCESS_ALIAS_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/;
|
|
2
|
+
const RESERVED_ACCESS_ALIASES = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3
|
+
const RESOURCE_TYPES = {
|
|
4
|
+
page: true,
|
|
5
|
+
database: true,
|
|
6
|
+
dataSource: true,
|
|
7
|
+
customAgent: true
|
|
8
|
+
};
|
|
9
|
+
function normalizeWorkflowAccess(declarations) {
|
|
10
|
+
const source = declarations ?? {};
|
|
11
|
+
assertPlainObject(source, "Workflow access must be an object keyed by alias.");
|
|
12
|
+
const keys = Object.keys(source);
|
|
13
|
+
if (Reflect.ownKeys(source).length !== keys.length) {
|
|
14
|
+
throw new Error("Workflow access aliases must be enumerable strings.");
|
|
15
|
+
}
|
|
16
|
+
if (keys.length > 100) {
|
|
17
|
+
throw new Error("Workflows support at most 100 access requirements.");
|
|
18
|
+
}
|
|
19
|
+
const normalized = /* @__PURE__ */ Object.create(null);
|
|
20
|
+
for (const alias of keys) {
|
|
21
|
+
if (!ACCESS_ALIAS_PATTERN.test(alias) || RESERVED_ACCESS_ALIASES.has(alias)) {
|
|
22
|
+
throw new Error(`Invalid workflow access alias: ${alias}`);
|
|
23
|
+
}
|
|
24
|
+
const declaration = source[alias];
|
|
25
|
+
if (!isRecord(declaration) || !Object.hasOwn(declaration, "resource") || !Object.hasOwn(declaration, "level")) {
|
|
26
|
+
throw new Error(`Workflow access "${alias}" must declare a resource and level.`);
|
|
27
|
+
}
|
|
28
|
+
const resource = declaration.resource;
|
|
29
|
+
if (!isRecord(resource) || !Object.hasOwn(resource, "resourceType") || !Object.hasOwn(resource, "resourceId") || typeof resource.resourceId !== "string" || resource.resourceId.length === 0 || !isWorkflowAccessResourceType(resource.resourceType)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Workflow access "${alias}" must reference a supported NaC resource handle.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
let requirement;
|
|
35
|
+
if (resource.resourceType === "customAgent") {
|
|
36
|
+
if (declaration.level !== "call") {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Workflow access "${alias}" custom agents only support level "call".`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
requirement = {
|
|
42
|
+
type: resource.resourceType,
|
|
43
|
+
resourceId: resource.resourceId,
|
|
44
|
+
level: declaration.level
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
if (!isWorkflowAccessResourceLevel(declaration.level)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Workflow access "${alias}" ${resource.resourceType} level must be view, comment, edit, or fullAccess.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
requirement = {
|
|
53
|
+
type: resource.resourceType,
|
|
54
|
+
resourceId: resource.resourceId,
|
|
55
|
+
level: declaration.level
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
normalized[alias] = Object.freeze(requirement);
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze(normalized);
|
|
61
|
+
}
|
|
62
|
+
function hydrateWorkflowAccess(requirements) {
|
|
63
|
+
const expected = requirements ?? {};
|
|
64
|
+
const aliases = Object.keys(expected);
|
|
65
|
+
const empty = /* @__PURE__ */ Object.create(null);
|
|
66
|
+
if (aliases.length === 0) {
|
|
67
|
+
return Object.freeze(empty);
|
|
68
|
+
}
|
|
69
|
+
const rawBindings = process.env.NOTION_ACCESS_BINDINGS;
|
|
70
|
+
if (rawBindings === void 0) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Workflow access requirements are not bound. Deploy and configure this requirement before running it."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (rawBindings.length === 0) {
|
|
76
|
+
throw new Error("Invalid NOTION_ACCESS_BINDINGS metadata.");
|
|
77
|
+
}
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(rawBindings);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new Error("Invalid NOTION_ACCESS_BINDINGS metadata.", { cause: error });
|
|
83
|
+
}
|
|
84
|
+
assertPlainObject(parsed, "Invalid NOTION_ACCESS_BINDINGS metadata.");
|
|
85
|
+
const bindingKeys = Object.keys(parsed);
|
|
86
|
+
if (Reflect.ownKeys(parsed).length !== bindingKeys.length || bindingKeys.length !== aliases.length || bindingKeys.some((alias) => !Object.hasOwn(expected, alias))) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Invalid NOTION_ACCESS_BINDINGS metadata: aliases do not match workflow access."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const resolved = /* @__PURE__ */ Object.create(null);
|
|
92
|
+
for (const alias of aliases) {
|
|
93
|
+
const requirement = expected[alias];
|
|
94
|
+
if (!requirement) {
|
|
95
|
+
throw new Error(`Workflow access "${alias}" is not declared.`);
|
|
96
|
+
}
|
|
97
|
+
const binding = parsed[alias];
|
|
98
|
+
if (!isBinding(binding)) {
|
|
99
|
+
throw new Error(`Invalid NOTION_ACCESS_BINDINGS metadata for access "${alias}".`);
|
|
100
|
+
}
|
|
101
|
+
if (binding.type !== requirement.type || binding.resourceId !== requirement.resourceId || binding.level !== requirement.level) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Workflow access "${alias}" binding does not match its declared type, resourceId, or level.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
resolved[alias] = Object.freeze({ type: binding.type, id: binding.id });
|
|
107
|
+
}
|
|
108
|
+
return Object.freeze(resolved);
|
|
109
|
+
}
|
|
110
|
+
function isWorkflowAccessResourceType(value) {
|
|
111
|
+
return typeof value === "string" && Object.hasOwn(RESOURCE_TYPES, value);
|
|
112
|
+
}
|
|
113
|
+
function isWorkflowAccessResourceLevel(value) {
|
|
114
|
+
return value === "view" || value === "comment" || value === "edit" || value === "fullAccess";
|
|
115
|
+
}
|
|
116
|
+
function isBinding(value) {
|
|
117
|
+
if (!isRecord(value) || Reflect.ownKeys(value).length !== 4) return false;
|
|
118
|
+
const keys = Object.keys(value);
|
|
119
|
+
if (keys.length !== 4 || !keys.includes("type") || !keys.includes("resourceId") || !keys.includes("level") || !keys.includes("id")) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return isWorkflowAccessResourceType(value.type) && typeof value.resourceId === "string" && value.resourceId.length > 0 && typeof value.level === "string" && (isWorkflowAccessResourceLevel(value.level) || value.level === "call") && typeof value.id === "string" && value.id.length > 0;
|
|
123
|
+
}
|
|
124
|
+
function assertPlainObject(value, message) {
|
|
125
|
+
if (!isRecord(value)) throw new Error(message);
|
|
126
|
+
const prototype = Object.getPrototypeOf(value);
|
|
127
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error(message);
|
|
128
|
+
}
|
|
129
|
+
function isRecord(value) {
|
|
130
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
hydrateWorkflowAccess,
|
|
134
|
+
normalizeWorkflowAccess
|
|
135
|
+
};
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type WorkflowConnectionDeclarations, type WorkflowConnectionRequirement, type WorkflowConnections } from "./connections.js";
|
|
2
|
+
import { type WorkflowAccess, type WorkflowAccessDeclarations, type WorkflowAccessRequirements } from "./workflow-access.js";
|
|
2
3
|
import type { CapabilityContext } from "./context.js";
|
|
3
4
|
import { type RunMetadata } from "./runtime-metadata.js";
|
|
4
5
|
import { type WorkflowState } from "./workflow-state.js";
|
|
@@ -37,6 +38,7 @@ export type WorkflowWaitUntilResult = {
|
|
|
37
38
|
type WorkflowHandlerResult = {
|
|
38
39
|
status: "success";
|
|
39
40
|
} | WorkflowWaitUntilResult;
|
|
41
|
+
export type { WorkflowAccess, WorkflowAccessAgentLevel, WorkflowAccessBindings, WorkflowAccessDeclaration, WorkflowAccessDeclarations, WorkflowAccessLevel, WorkflowAccessLevelFor, WorkflowAccessResource, WorkflowAccessResourceLevel, WorkflowAccessResourceType, WorkflowAccessRequirement, WorkflowAccessRequirements, WorkflowAccessValue, } from "./workflow-access.js";
|
|
40
42
|
export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
|
|
41
43
|
export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMap[T["type"]];
|
|
42
44
|
export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> = WorkflowEventForTrigger<T[number]>;
|
|
@@ -44,7 +46,7 @@ export { connections, type WorkflowConnection, type WorkflowConnectionDeclaratio
|
|
|
44
46
|
/**
|
|
45
47
|
* Configuration passed to {@link workflow}.
|
|
46
48
|
*/
|
|
47
|
-
export type WorkflowConfiguration<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations> = {
|
|
49
|
+
export type WorkflowConfiguration<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations, TAccess extends WorkflowAccessDeclarations = WorkflowAccessDeclarations> = {
|
|
48
50
|
/**
|
|
49
51
|
* A human-readable name for the workflow, shown in the UI when viewing workflows
|
|
50
52
|
*/
|
|
@@ -59,14 +61,20 @@ export type WorkflowConfiguration<TTriggers extends readonly [WorkflowTrigger, .
|
|
|
59
61
|
* Each trigger defines a specific event or condition that causes the workflow to run.
|
|
60
62
|
*/
|
|
61
63
|
triggers: TTriggers;
|
|
64
|
+
/**
|
|
65
|
+
* Notion-as-Code resources whose access is configured during cloud deployment.
|
|
66
|
+
* Deployment reconciles grants through the existing Notion-module permissions
|
|
67
|
+
* and supplies named runtime bindings on `context.access`.
|
|
68
|
+
*/
|
|
69
|
+
access?: TAccess;
|
|
62
70
|
/**
|
|
63
71
|
* Connections that must be set up before this workflow can run.
|
|
64
72
|
*/
|
|
65
73
|
connections?: TConnections;
|
|
66
|
-
handler: (event: WorkflowEventForTriggers<TTriggers>, context: WorkflowContext<TConnections>) => Promise<void> | void;
|
|
74
|
+
handler: (event: WorkflowEventForTriggers<TTriggers>, context: WorkflowContext<TConnections, TAccess>) => Promise<void> | void;
|
|
67
75
|
};
|
|
68
76
|
/** Configuration that checks trigger keys against declared connections. */
|
|
69
|
-
export type WorkflowTriggerConfiguration<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], TConnections extends WorkflowConnectionDeclarations> = Omit<WorkflowConfiguration<TTriggers, TConnections>, "triggers"> & {
|
|
77
|
+
export type WorkflowTriggerConfiguration<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], TConnections extends WorkflowConnectionDeclarations, TAccess extends WorkflowAccessDeclarations = WorkflowAccessDeclarations> = Omit<WorkflowConfiguration<TTriggers, TConnections, TAccess>, "triggers"> & {
|
|
70
78
|
triggers: (context: {
|
|
71
79
|
triggers: WorkflowTriggerCreators<NoInfer<TConnections>>;
|
|
72
80
|
}) => TTriggers;
|
|
@@ -87,6 +95,7 @@ export type Workflow<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTri
|
|
|
87
95
|
description: string;
|
|
88
96
|
triggers: TTriggers;
|
|
89
97
|
connections?: readonly WorkflowConnectionRequirement[];
|
|
98
|
+
access?: WorkflowAccessRequirements;
|
|
90
99
|
};
|
|
91
100
|
handler: (event: WorkflowEventForTriggers<TTriggers>, options?: HandlerOptions) => Promise<WorkflowHandlerResult | undefined>;
|
|
92
101
|
};
|
|
@@ -119,8 +128,8 @@ export type Workflow<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTri
|
|
|
119
128
|
export declare function workflow<const TTriggers extends readonly [
|
|
120
129
|
WorkflowTriggerForConnections<NoInfer<TConnections>>,
|
|
121
130
|
...WorkflowTriggerForConnections<NoInfer<TConnections>>[]
|
|
122
|
-
], const TConnections extends WorkflowConnectionDeclarations = Record<never, never>>(configuration: WorkflowTriggerConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
123
|
-
export declare function workflow<const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], const TConnections extends WorkflowConnectionDeclarations = Record<never, never>>(configuration: WorkflowConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
131
|
+
], const TConnections extends WorkflowConnectionDeclarations = Record<never, never>, const TAccess extends WorkflowAccessDeclarations = Record<never, never>>(configuration: WorkflowTriggerConfiguration<TTriggers, TConnections, TAccess>): Workflow<TTriggers>;
|
|
132
|
+
export declare function workflow<const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]], const TConnections extends WorkflowConnectionDeclarations = Record<never, never>, const TAccess extends WorkflowAccessDeclarations = Record<never, never>>(configuration: WorkflowConfiguration<TTriggers, TConnections, TAccess>): Workflow<TTriggers>;
|
|
124
133
|
/** Context passed to a workflow step. */
|
|
125
134
|
export type StepContext = {
|
|
126
135
|
/**
|
|
@@ -158,7 +167,9 @@ type WorkflowWait = {
|
|
|
158
167
|
until(name: string, options: WorkflowWaitUntilOptions): Promise<void>;
|
|
159
168
|
};
|
|
160
169
|
/** Context passed to a workflow handler. */
|
|
161
|
-
export type WorkflowContext<TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations> = CapabilityContext & {
|
|
170
|
+
export type WorkflowContext<TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations, TAccess extends WorkflowAccessDeclarations = WorkflowAccessDeclarations> = CapabilityContext & {
|
|
171
|
+
/** Resolved NaC bindings for this invocation; UI-granted resources are not added here. */
|
|
172
|
+
readonly access: WorkflowAccess<TAccess>;
|
|
162
173
|
connections: WorkflowConnections<TConnections>;
|
|
163
174
|
} & RunMetadata & {
|
|
164
175
|
/**
|
package/dist/workflow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow.js
CHANGED
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
normalizeConnectionRequirements,
|
|
4
4
|
validateTriggerConnections
|
|
5
5
|
} from "./connections.js";
|
|
6
|
+
import {
|
|
7
|
+
hydrateWorkflowAccess,
|
|
8
|
+
normalizeWorkflowAccess
|
|
9
|
+
} from "./workflow-access.js";
|
|
6
10
|
import { createHash } from "node:crypto";
|
|
7
11
|
import { readFile } from "node:fs/promises";
|
|
8
12
|
import { join } from "node:path";
|
|
@@ -19,6 +23,7 @@ import {
|
|
|
19
23
|
} from "./connections.js";
|
|
20
24
|
function workflow(configuration) {
|
|
21
25
|
const requirements = normalizeConnectionRequirements(configuration.connections ?? {});
|
|
26
|
+
const accessRequirements = normalizeWorkflowAccess(configuration.access);
|
|
22
27
|
const connectionDeclarations = configuration.connections === void 0 ? void 0 : structuredClone(configuration.connections);
|
|
23
28
|
const triggers = typeof configuration.triggers === "function" ? configuration.triggers({ triggers: createWorkflowTriggers() }) : configuration.triggers;
|
|
24
29
|
validateTriggerConnections(triggers, requirements);
|
|
@@ -28,7 +33,8 @@ function workflow(configuration) {
|
|
|
28
33
|
name: configuration.name,
|
|
29
34
|
description: configuration.description,
|
|
30
35
|
triggers,
|
|
31
|
-
...configuration.connections === void 0 ? {} : { connections: requirements }
|
|
36
|
+
...configuration.connections === void 0 ? {} : { connections: requirements },
|
|
37
|
+
...configuration.access === void 0 ? {} : { access: accessRequirements }
|
|
32
38
|
},
|
|
33
39
|
async handler(event, options) {
|
|
34
40
|
try {
|
|
@@ -38,6 +44,7 @@ function workflow(configuration) {
|
|
|
38
44
|
const step = createStep(runMetadata);
|
|
39
45
|
const capabilityContext = {
|
|
40
46
|
...baseContext,
|
|
47
|
+
access: hydrateWorkflowAccess(accessRequirements),
|
|
41
48
|
connections: createWorkflowConnections(
|
|
42
49
|
baseContext.notion,
|
|
43
50
|
connectionDeclarations
|
package/docs/BUILD.md
CHANGED
|
@@ -43,10 +43,11 @@ natural capability owner.
|
|
|
43
43
|
1. Discover top-level files in the capability directories.
|
|
44
44
|
2. Generate `.notion/entry.ts` with workflow and sync imports and a dispatcher.
|
|
45
45
|
3. Bundle the app's code into `dist/worker.js`, leaving npm packages external.
|
|
46
|
-
4. Evaluate custom-block declarations separately
|
|
47
|
-
configuration, and write `dist/manifest.json`. Blocks do not enter `worker.js`.
|
|
46
|
+
4. Evaluate custom-block declarations separately. Blocks do not enter `worker.js`.
|
|
48
47
|
5. Evaluate worker capabilities in a separate build-only metadata bundle to record
|
|
49
|
-
Notion-as-Code declarations
|
|
48
|
+
Notion-as-Code declarations. Validate workflow access references against those
|
|
49
|
+
declarations before writing the manifest and optional provisioning artifact.
|
|
50
|
+
6. Emit `dist/manifest.json` and, when declarations exist, `dist/provisioning.json`.
|
|
50
51
|
|
|
51
52
|
Capability modules must therefore be importable without secrets or network access.
|
|
52
53
|
Read required environment variables and make requests inside handlers or workflow
|
|
@@ -170,7 +171,8 @@ uploaded project must declare its SDK dependency and a `build` script that invok
|
|
|
170
171
|
Worker using `workersCreateWorker` with `createApp: true`, or updates the existing
|
|
171
172
|
Worker, then uploads source and calls `workersBuildWorker` with the Worker ID.
|
|
172
173
|
For App-linked Workers, the build endpoint coordinates capability registration,
|
|
173
|
-
Notion-as-Code provisioning,
|
|
174
|
+
Notion-as-Code provisioning, database attachment, workflow binding resolution,
|
|
175
|
+
and workflow permission reconciliation before reporting build success.
|
|
174
176
|
It returns the normal build result and run ID, not provisioning state. No separate
|
|
175
177
|
Apps deployment endpoint is required.
|
|
176
178
|
|
|
@@ -193,6 +195,9 @@ flow and local state, and reconciles attachments. It reads the optional
|
|
|
193
195
|
`dist/provisioning.json` locally and does not upload it separately or invoke
|
|
194
196
|
the cloud build endpoint.
|
|
195
197
|
|
|
198
|
+
Workflows with nonempty `access` require cloud deployment. The local-build path
|
|
199
|
+
rejects them after the SDK build and before uploading or deploying the bundle.
|
|
200
|
+
|
|
196
201
|
Resource bindings are not stored in SDK build output. Local deployments retain a
|
|
197
202
|
local state file and report `state_file`; cloud deployments keep installation-owned
|
|
198
203
|
resource mappings and state on the server. Cloud builds do not import local state
|
|
@@ -202,6 +207,35 @@ switching between modes may recreate resources rather than reuse existing bindin
|
|
|
202
207
|
Cloud failures never automatically retry locally. There is no rollback: code or
|
|
203
208
|
resources may already have changed when a deployment fails.
|
|
204
209
|
|
|
210
|
+
## Workflow access
|
|
211
|
+
|
|
212
|
+
`workflow({ access: { handbook: { resource: handbook, level: "view" } }, ... })`
|
|
213
|
+
declares access to a NaC resource and a named runtime binding. The manifest
|
|
214
|
+
contains only the alias and its symbolic `{ type, resourceId, level }`
|
|
215
|
+
requirement; live IDs are not build output.
|
|
216
|
+
Build validation rejects references missing from the current provisioning
|
|
217
|
+
declarations or having the wrong resource kind. `resourceId` is required because
|
|
218
|
+
it identifies the declaration used for this validation and binding.
|
|
219
|
+
|
|
220
|
+
After provisioning, cloud deployment resolves requirements against the successful
|
|
221
|
+
NaC apply result. It reconciles code-managed grants through the existing
|
|
222
|
+
Notion-module permissions and saves those permissions together with resolved
|
|
223
|
+
bindings on the workflow's worker module, keyed by capability and alias, in one
|
|
224
|
+
workflow transaction. Bindings are not authorization: the existing permissions
|
|
225
|
+
authorize `context.notion` calls. Redeployment updates or removes code-managed
|
|
226
|
+
grants as declarations change while preserving manually configured UI grants.
|
|
227
|
+
|
|
228
|
+
Execution reads the selected workflow configuration, not the latest NaC state,
|
|
229
|
+
validates the bindings against the capability requirements, and injects only that
|
|
230
|
+
capability's declared bindings. The SDK exposes typed, readonly `{ type, id }`
|
|
231
|
+
entries in `context.access`; UI-granted resources do not appear there. Use live
|
|
232
|
+
record IDs directly in `context.notion` calls for UI-granted resources; those
|
|
233
|
+
calls remain subject to the existing permissions. Missing or mismatched bindings
|
|
234
|
+
fail before the handler executes.
|
|
235
|
+
|
|
236
|
+
See the [workflow skill](../skills/workflow/SKILL.md#resources-created-with-the-app)
|
|
237
|
+
for supported resource types, access levels, and an authoring example.
|
|
238
|
+
|
|
205
239
|
## Manifest
|
|
206
240
|
|
|
207
241
|
The workflow-only manifest retains the platform's existing resource fields as empty arrays:
|
package/package.json
CHANGED
|
@@ -24,6 +24,14 @@ owned by one capability, use a local module such as
|
|
|
24
24
|
are shared across capabilities or do not have a natural capability owner. Module
|
|
25
25
|
evaluation must work without credentials or network access.
|
|
26
26
|
|
|
27
|
+
To configure a workflow's access to a declared page, database, data source, or
|
|
28
|
+
custom agent, pass its handle in `access: { alias: { resource: handle, level } }`.
|
|
29
|
+
The handler receives the live identity as `context.access.alias.id`; importing
|
|
30
|
+
a declaration alone does not request access. See the
|
|
31
|
+
[workflow skill](../workflow/SKILL.md#resources-created-with-the-app) for levels
|
|
32
|
+
and examples. Nonempty workflow `access` requires cloud deployment; `--local-build`
|
|
33
|
+
rejects it before upload.
|
|
34
|
+
|
|
27
35
|
## Where declarations are picked up
|
|
28
36
|
|
|
29
37
|
The Apps build discovers direct `.ts` children of `src/workflows/` and
|
|
@@ -67,7 +75,8 @@ root exports support:
|
|
|
67
75
|
|
|
68
76
|
- `teamspace({ resourceId, name, accessLevel })`, with `addPage` and
|
|
69
77
|
`teamspace.addDatabase(id, args)` on the returned handle.
|
|
70
|
-
- `page({ resourceId, parent?, properties?, content? })
|
|
78
|
+
- `page({ resourceId, parent?, properties?, content? })`; regular page titles use
|
|
79
|
+
`properties: { title: notion.text("My Page Title") }`. It also has
|
|
71
80
|
`addPage` and `page.addDatabase(id, args)` for children.
|
|
72
81
|
- `database(resourceId, { dataSourceResourceId, name, schema, parent?, views? })`,
|
|
73
82
|
returning a single source as `handle.dataSource`, or
|
|
@@ -82,6 +91,11 @@ root exports support:
|
|
|
82
91
|
Use property resource IDs in view filters, sorts, and layout options; calendar
|
|
83
92
|
views require `calendarBy`, and timeline views require `timelineBy`.
|
|
84
93
|
|
|
94
|
+
For a regular page, set the title with
|
|
95
|
+
`properties: { title: notion.text("My Page Title") }`. A Markdown heading in
|
|
96
|
+
`content` creates a child heading block; it does not set the page title. For a
|
|
97
|
+
database page, use the title property's schema name instead of `title`.
|
|
98
|
+
|
|
85
99
|
A database declaration must include at least one data source or at least one
|
|
86
100
|
view. A linked-only database may omit `datasources`, or use `datasources: {}`,
|
|
87
101
|
only when `views` is nonempty. `{}`, `{ datasources: {} }`, and
|
|
@@ -123,8 +137,7 @@ The Apps SDK exposes a subset of Notion as Code. Data sources are nested inside
|
|
|
123
137
|
Apps deployment rejects workspace creation or changes and supplies the App's
|
|
124
138
|
workspace binding itself. Value helpers and types stay on their existing
|
|
125
139
|
subpaths. For example, import `{ notion }` from `@notionhq/apps/notion-as-code`
|
|
126
|
-
for `notion.text(...)
|
|
127
|
-
reference, not an upload or file declaration. These helpers are not root exports.
|
|
140
|
+
for `notion.text(...)`. These helpers are not root exports.
|
|
128
141
|
CLI acceptance of a serialized intent envelope alone does not establish server
|
|
129
142
|
support for its contents.
|
|
130
143
|
|
package/skills/workflow/SKILL.md
CHANGED
|
@@ -110,8 +110,8 @@ configuration, and do not log secrets or private payloads.
|
|
|
110
110
|
|
|
111
111
|
## Resources created with the App
|
|
112
112
|
|
|
113
|
-
Use [Notion as Code](../notion-as-code/SKILL.md) for pages and
|
|
114
|
-
should be created during deployment. Prefer it over equivalent manual setup
|
|
113
|
+
Use [Notion as Code](../notion-as-code/SKILL.md) for pages, databases, and custom agents
|
|
114
|
+
that should be created during deployment. Prefer it over equivalent manual setup
|
|
115
115
|
or API calls that create the App's resources. Use runtime API calls for dynamic
|
|
116
116
|
data changes, not as a substitute for supported Notion as Code setup.
|
|
117
117
|
Keep declarations at module scope. A small workflow-specific resource can be
|
|
@@ -120,39 +120,76 @@ guide page in `src/workflows/sayHello/lib/notion.ts`:
|
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
122
|
import { page } from "@notionhq/apps";
|
|
123
|
+
import { notion } from "@notionhq/apps/notion-as-code";
|
|
123
124
|
|
|
124
125
|
export const guide = page({
|
|
125
126
|
resourceId: "workflow-guide",
|
|
126
|
-
|
|
127
|
+
properties: { title: notion.text("Workflow guide") },
|
|
128
|
+
content: "This App runs a scheduled workflow.",
|
|
127
129
|
});
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
Import the
|
|
132
|
+
Import the handle into the workflow and declare the named binding it needs:
|
|
131
133
|
|
|
132
134
|
```ts
|
|
133
135
|
import { workflow } from "@notionhq/apps";
|
|
134
136
|
import { triggers } from "@notionhq/apps/triggers";
|
|
135
137
|
|
|
136
|
-
import "./sayHello/lib/notion.js";
|
|
138
|
+
import { guide } from "./sayHello/lib/notion.js";
|
|
137
139
|
|
|
138
140
|
export default workflow({
|
|
139
|
-
name: "
|
|
140
|
-
description: "
|
|
141
|
+
name: "Read guide",
|
|
142
|
+
description: "Reads the App's guide on a recurring schedule.",
|
|
141
143
|
triggers: [triggers.scheduled()],
|
|
144
|
+
access: { guide: { resource: guide, level: "view" } },
|
|
142
145
|
handler: async (_event, context) => {
|
|
143
|
-
await context.step("
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
await context.step("Read guide", () =>
|
|
147
|
+
context.notion.pages.retrieve({ page_id: context.access.guide.id }),
|
|
148
|
+
);
|
|
146
149
|
},
|
|
147
150
|
});
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
The page is provisioned during deployment, not on every workflow run.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
The page is provisioned during deployment, not on every workflow run. Inline
|
|
154
|
+
declarations in `access` work too. Importing a declaration alone provisions the
|
|
155
|
+
resource but does not create a `context.access` alias. An `access` entry declares
|
|
156
|
+
the needed permission level and a named binding for that NaC resource.
|
|
157
|
+
|
|
158
|
+
`access` accepts page, database, data-source, and custom-agent handles. Pages,
|
|
159
|
+
databases, and data sources support `view`, `comment`, `edit`, and `fullAccess`;
|
|
160
|
+
custom agents support only `call`. Keys are workflow-local aliases. The handler's
|
|
161
|
+
typed `context.access` exposes each declared alias as a readonly `{ type, id }`,
|
|
162
|
+
not the declaration handle or a permission-management API. UI-granted resources
|
|
163
|
+
are permissions only: they do not create `context.access` aliases.
|
|
164
|
+
|
|
165
|
+
For a resource already granted to the workflow in Notion's UI, use its live
|
|
166
|
+
record ID directly in a `context.notion` call:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
await context.step("Read granted page", () =>
|
|
170
|
+
context.notion.pages.retrieve({ page_id: "page-id" }),
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Such a UI grant needs no `access` entry, NaC declaration, or NaC `resourceId`.
|
|
175
|
+
The call remains subject to the workflow's existing Notion-module permissions.
|
|
176
|
+
|
|
177
|
+
Use a data-source handle when calling `context.notion.dataSources`: its resolved
|
|
178
|
+
ID is a collection ID. A database handle resolves to the database block ID;
|
|
179
|
+
these identities are not interchangeable. Notion as Code `resourceId` values
|
|
180
|
+
remain declaration identities, not live Notion UUIDs.
|
|
181
|
+
|
|
182
|
+
Cloud deployment resolves declared handles to live IDs and reconciles their
|
|
183
|
+
grants through the existing Notion-module permissions. It saves permissions and
|
|
184
|
+
bindings together; bindings themselves do not authorize calls. Changing `access`
|
|
185
|
+
updates code-managed grants and bindings while preserving manually configured
|
|
186
|
+
UI grants. Normal draft/publish and runtime authorization still apply. Missing
|
|
187
|
+
or mismatched required bindings fail before the handler runs.
|
|
188
|
+
|
|
189
|
+
Deploy workflows with nonempty `access` using `ntn apps deploy`, without
|
|
190
|
+
`--local-build`. Local-build deployment rejects them before upload. Runtime API
|
|
191
|
+
calls still belong in durable steps. For syncs, see the
|
|
192
|
+
[data-source example](../sync/SKILL.md#choose-the-database-source).
|
|
156
193
|
|
|
157
194
|
## Review a workflow
|
|
158
195
|
|
package/src/cli/build.test.ts
CHANGED
|
@@ -204,6 +204,30 @@ describe("buildApp", () => {
|
|
|
204
204
|
/hello.ts.*customBlock/,
|
|
205
205
|
);
|
|
206
206
|
});
|
|
207
|
+
it("emits symbolic access requirements for every NaC resource kind", async () => {
|
|
208
|
+
const { manifest } = await buildApp(fixture("workflow-access"));
|
|
209
|
+
expect(manifest.capabilities).toEqual([
|
|
210
|
+
{
|
|
211
|
+
type: "workflow",
|
|
212
|
+
key: "access",
|
|
213
|
+
config: {
|
|
214
|
+
name: "Access",
|
|
215
|
+
description: "Declares all workflow access resource kinds",
|
|
216
|
+
triggers: [{ type: "notion.page.created" }],
|
|
217
|
+
access: {
|
|
218
|
+
home: { type: "page", resourceId: "home", level: "view" },
|
|
219
|
+
records: { type: "database", resourceId: "records", level: "edit" },
|
|
220
|
+
source: {
|
|
221
|
+
type: "dataSource",
|
|
222
|
+
resourceId: "records-source",
|
|
223
|
+
level: "comment",
|
|
224
|
+
},
|
|
225
|
+
agent: { type: "customAgent", resourceId: "agent", level: "call" },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
207
231
|
|
|
208
232
|
it("builds a sync manifest with a Notion-as-Code meetings database", async () => {
|
|
209
233
|
const { manifest } = await buildApp(example("sync"));
|
package/src/cli/build.ts
CHANGED
|
@@ -54,24 +54,29 @@ export async function buildApp(projectRoot: string): Promise<BuildResult> {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const blockConfigs = await buildBlockDeclarations(projectRoot, capabilities);
|
|
57
|
-
const
|
|
58
|
-
const manifestPath = path.join(projectRoot, "dist", "manifest.json");
|
|
59
|
-
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
|
|
60
|
-
|
|
61
|
-
await emitProvisioningArtifact(
|
|
57
|
+
const entityProvisioning = await buildMetadata(
|
|
62
58
|
projectRoot,
|
|
63
59
|
capabilities.filter((capability) => capability.runtime === "worker"),
|
|
64
60
|
);
|
|
61
|
+
const manifest = await extractManifest(
|
|
62
|
+
bundlePath,
|
|
63
|
+
capabilities,
|
|
64
|
+
blockConfigs,
|
|
65
|
+
entityProvisioning,
|
|
66
|
+
);
|
|
67
|
+
const manifestPath = path.join(projectRoot, "dist", "manifest.json");
|
|
68
|
+
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
|
|
69
|
+
|
|
70
|
+
await emitProvisioningArtifact(projectRoot, entityProvisioning);
|
|
65
71
|
|
|
66
72
|
return { manifest, bundlePath, manifestPath };
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
/**
|
|
75
|
+
/** Emit the Notion-as-Code artifact recorded by the build-only metadata bundle. */
|
|
70
76
|
async function emitProvisioningArtifact(
|
|
71
77
|
projectRoot: string,
|
|
72
|
-
|
|
78
|
+
entityProvisioning: RecordedProvisioning,
|
|
73
79
|
): Promise<void> {
|
|
74
|
-
const entityProvisioning = await buildMetadata(projectRoot, capabilities);
|
|
75
80
|
const provisioningPath = path.join(projectRoot, "dist", "provisioning.json");
|
|
76
81
|
if (entityProvisioning.intents.length === 0) {
|
|
77
82
|
await fs.promises.rm(provisioningPath, { force: true });
|