@notionhq/custom-blocks 0.1.11 → 0.1.13
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 +0 -1
- package/dist/bridge/loadManifest.d.ts +3 -4
- package/dist/bridge/loadManifest.d.ts.map +1 -1
- package/dist/bridge/loadManifest.js +11 -5
- package/dist/init.js +1 -1
- package/dist/version.js +1 -1
- package/docs/deployment.md +7 -29
- package/package.json +1 -1
- package/src/bridge/loadManifest.ts +14 -5
- package/src/init.ts +1 -1
- package/vite-plugin/index.d.ts +1 -39
- package/vite-plugin/index.js +4 -112
- package/docs/vite-plugin.md +0 -22
package/README.md
CHANGED
|
@@ -104,7 +104,6 @@ API surface, one page per category. Import framework-neutral APIs from `@notionh
|
|
|
104
104
|
- [`docs/users.md`](./docs/users.md) — `users.list / get`, the `NotionUser` shape, paging.
|
|
105
105
|
- [`docs/errors.md`](./docs/errors.md) — request results, error format, error codes, retries, and initialization failures.
|
|
106
106
|
- [`docs/deployment.md`](./docs/deployment.md) — worker-backed deploys, localhost self-hosted fallback, where the manifest comes from.
|
|
107
|
-
- [`docs/vite-plugin.md`](./docs/vite-plugin.md) — the `notionCustomBlock()` Vite plugin.
|
|
108
107
|
|
|
109
108
|
## Forbidden APIs
|
|
110
109
|
|
|
@@ -10,12 +10,11 @@ export type ManifestLoadResult = {
|
|
|
10
10
|
error: CustomBlockConnectError;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* local dev server.
|
|
13
|
+
* On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
|
|
14
|
+
* manifest exposed by the worker dev shell at `/manifest`.
|
|
16
15
|
*
|
|
17
16
|
* It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
|
|
18
17
|
*/
|
|
19
|
-
export declare function attemptToLoadSelfHostedManifest(): Promise<ManifestLoadResult>;
|
|
18
|
+
export declare function attemptToLoadSelfHostedManifest(hostname?: string | undefined): Promise<ManifestLoadResult>;
|
|
20
19
|
export {};
|
|
21
20
|
//# sourceMappingURL=loadManifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AACtF,OAAO,EACN,KAAK,mBAAmB,EAExB,MAAM,8CAA8C,CAAA;AAKrD,KAAK,2BAA2B,GAC7B,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,KAAK,uBAAuB,GAAG,oBAAoB,CAAC,2BAA2B,CAAC,CAAA;AAEhF,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,uBAAuB,CAAA;CAC7B,CAAA;AAEJ
|
|
1
|
+
{"version":3,"file":"loadManifest.d.ts","sourceRoot":"","sources":["../../../../home/runner/work/custom-blocks/custom-blocks/sdk/src/bridge/loadManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AACtF,OAAO,EACN,KAAK,mBAAmB,EAExB,MAAM,8CAA8C,CAAA;AAKrD,KAAK,2BAA2B,GAC7B,sBAAsB,GACtB,kBAAkB,GAClB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEhB,KAAK,uBAAuB,GAAG,oBAAoB,CAAC,2BAA2B,CAAC,CAAA;AAEhF,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;CAChB,GACD;IACA,QAAQ,EAAE,IAAI,CAAA;IACd,KAAK,EAAE,uBAAuB,CAAA;CAC7B,CAAA;AAEJ;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACpD,QAAQ,GAAE,MAAM,GAAG,SAEQ,GACzB,OAAO,CAAC,kBAAkB,CAAC,CAgE7B"}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { manifestSchema, } from "../protocol/manifest.js";
|
|
2
2
|
import * as v from "valibot";
|
|
3
|
-
const MANIFEST_URL = "
|
|
3
|
+
const MANIFEST_URL = "/manifest";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* local dev server.
|
|
5
|
+
* On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
|
|
6
|
+
* manifest exposed by the worker dev shell at `/manifest`.
|
|
8
7
|
*
|
|
9
8
|
* It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
|
|
10
9
|
*/
|
|
11
|
-
export async function attemptToLoadSelfHostedManifest(
|
|
10
|
+
export async function attemptToLoadSelfHostedManifest(hostname = typeof window === "undefined"
|
|
11
|
+
? undefined
|
|
12
|
+
: window.location.hostname) {
|
|
13
|
+
// Runtime manifests are a localhost-only development fallback. Deployed
|
|
14
|
+
// blocks receive their authoritative manifest from the host during init.
|
|
15
|
+
if (hostname !== "localhost") {
|
|
16
|
+
return { manifest: null };
|
|
17
|
+
}
|
|
12
18
|
let response;
|
|
13
19
|
try {
|
|
14
20
|
response = await fetch(MANIFEST_URL, { credentials: "omit" });
|
package/dist/init.js
CHANGED
|
@@ -39,7 +39,7 @@ export function initCustomBlock(opts = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Performs the host <-> sandbox SDK handshake:
|
|
42
|
-
* 1.
|
|
42
|
+
* 1. On localhost, attempts to load the worker manifest from `/manifest`
|
|
43
43
|
* 2. Sends `connect` with the manifest
|
|
44
44
|
* 3. Awaits the host's `init` message
|
|
45
45
|
* 4. Applies the `init` payload and acknowledges with `initResult` (fire-and-forget)
|
package/dist/version.js
CHANGED
package/docs/deployment.md
CHANGED
|
@@ -1,39 +1,17 @@
|
|
|
1
1
|
# Deployment and manifests
|
|
2
2
|
|
|
3
|
-
A custom block is a static web bundle — an `index.html` plus optional assets — that Notion loads into a sandboxed `<iframe
|
|
3
|
+
A custom block is a static web bundle — an `index.html` plus optional assets — that Notion loads into a sandboxed `<iframe>`. Custom blocks ship as part of a worker: declare the block source and data source schema with `worker.customBlock(...)` in the worker's `src/index.ts`, deploy with `ntn workers deploy`, and Notion serves the built bundle.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A custom block manifest describes the data sources a block requires, including their semantic keys, display metadata, and property schemas. The block's `worker.customBlock(...)` declaration is the manifest's single source of truth:
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
7
|
+
- **Deployed** — deploying persists the manifest on the block's definition record in Notion. The host reads it from there and sends it to the SDK during initialization.
|
|
8
|
+
- **Local development** — the dev shell extracts the same worker declaration at runtime and serves the selected block's manifest dynamically at `/manifest`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Local development
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Run `ntn customblocks dev` from a worker. It builds the worker, extracts its custom block declarations, starts one localhost Vite server per block, and renders them in the mock host at http://localhost:9873.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
During development, a Notion host can point a custom block at a local dev server instead of a deployed worker. The block describes itself with an optional `custom_blocks.json` at the bundle root:
|
|
17
|
-
|
|
18
|
-
```json
|
|
19
|
-
{
|
|
20
|
-
"version": 1,
|
|
21
|
-
"dataSources": {
|
|
22
|
-
"tasks": {
|
|
23
|
-
"name": "Tasks",
|
|
24
|
-
"description": "The collection of tasks to render",
|
|
25
|
-
"properties": {
|
|
26
|
-
"title": { "name": "Title", "type": "title" },
|
|
27
|
-
"dueDate": { "name": "Due date", "type": "date" }
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
At startup the SDK fetches this file from the bundle's origin and forwards it in `connect`. If the file is missing, the SDK omits `manifest` from `connect`. If it is present but unreadable or invalid, initialization fails.
|
|
35
|
-
|
|
36
|
-
The [Vite plugin](./vite-plugin.md) keeps this file working in dev and emits it into `dist/` on build.
|
|
14
|
+
At startup, the SDK fetches `/manifest` only when the bundle's hostname is exactly `localhost`, then forwards the response in `connect`. On every other hostname, the SDK skips this fetch and relies on the host-provided manifest from `init`.
|
|
37
15
|
|
|
38
16
|
## Types
|
|
39
17
|
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "@notionhq/custom-blocks-protocol/manifest.js"
|
|
6
6
|
import * as v from "valibot"
|
|
7
7
|
|
|
8
|
-
const MANIFEST_URL = "
|
|
8
|
+
const MANIFEST_URL = "/manifest"
|
|
9
9
|
|
|
10
10
|
type CustomBlockConnectErrorCode =
|
|
11
11
|
| "manifest_unavailable"
|
|
@@ -25,13 +25,22 @@ export type ManifestLoadResult =
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* local dev server.
|
|
28
|
+
* On the literal `localhost` hostname, attempts to load the custom block's optional self-hosted
|
|
29
|
+
* manifest exposed by the worker dev shell at `/manifest`.
|
|
31
30
|
*
|
|
32
31
|
* It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
|
|
33
32
|
*/
|
|
34
|
-
export async function attemptToLoadSelfHostedManifest(
|
|
33
|
+
export async function attemptToLoadSelfHostedManifest(
|
|
34
|
+
hostname: string | undefined = typeof window === "undefined"
|
|
35
|
+
? undefined
|
|
36
|
+
: window.location.hostname,
|
|
37
|
+
): Promise<ManifestLoadResult> {
|
|
38
|
+
// Runtime manifests are a localhost-only development fallback. Deployed
|
|
39
|
+
// blocks receive their authoritative manifest from the host during init.
|
|
40
|
+
if (hostname !== "localhost") {
|
|
41
|
+
return { manifest: null }
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
let response: Response
|
|
36
45
|
try {
|
|
37
46
|
response = await fetch(MANIFEST_URL, { credentials: "omit" })
|
package/src/init.ts
CHANGED
|
@@ -83,7 +83,7 @@ export function initCustomBlock(
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Performs the host <-> sandbox SDK handshake:
|
|
86
|
-
* 1.
|
|
86
|
+
* 1. On localhost, attempts to load the worker manifest from `/manifest`
|
|
87
87
|
* 2. Sends `connect` with the manifest
|
|
88
88
|
* 3. Awaits the host's `init` message
|
|
89
89
|
* 4. Applies the `init` payload and acknowledges with `initResult` (fire-and-forget)
|
package/vite-plugin/index.d.ts
CHANGED
|
@@ -1,46 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type declarations for the Vite plugin. The runtime lives in `vite.js` —
|
|
3
|
-
* see that file for behavior, options, and the reasoning for splitting `.js`
|
|
4
|
-
* + `.d.ts` instead of using a single `.ts` source.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
type UserConfig = { base?: string }
|
|
8
|
-
|
|
9
|
-
type ResolvedConfig = { root: string; command: "serve" | "build" }
|
|
10
|
-
|
|
11
|
-
type ServerLike = {
|
|
12
|
-
middlewares: { use: (handler: MiddlewareHandler) => unknown }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type MiddlewareHandler = (
|
|
16
|
-
req: { url?: string },
|
|
17
|
-
res: {
|
|
18
|
-
statusCode?: number
|
|
19
|
-
setHeader: (name: string, value: string) => void
|
|
20
|
-
end: (body?: string) => void
|
|
21
|
-
},
|
|
22
|
-
next: () => void,
|
|
23
|
-
) => void
|
|
24
|
-
|
|
25
|
-
type AssetEmitterContext = {
|
|
26
|
-
emitFile: (file: {
|
|
27
|
-
type: "asset"
|
|
28
|
-
fileName: string
|
|
29
|
-
source: string
|
|
30
|
-
}) => string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
1
|
export type NotionCustomBlockPlugin = {
|
|
34
2
|
name: string
|
|
35
|
-
config: (userConfig: UserConfig) => { base: string }
|
|
36
|
-
configResolved: (config: ResolvedConfig) => void
|
|
37
|
-
configureServer: (server: ServerLike) => void
|
|
38
|
-
configurePreviewServer: (server: ServerLike) => void
|
|
39
|
-
buildStart: (this: AssetEmitterContext) => void
|
|
40
3
|
}
|
|
41
4
|
|
|
42
5
|
/**
|
|
43
|
-
*
|
|
44
|
-
* bundle during local development and emits it as a separate asset on build.
|
|
6
|
+
* @deprecated This compatibility stub does nothing. Remove it from Vite config when possible.
|
|
45
7
|
*/
|
|
46
8
|
export function notionCustomBlock(): NotionCustomBlockPlugin
|
package/vite-plugin/index.js
CHANGED
|
@@ -1,116 +1,8 @@
|
|
|
1
|
+
// Compatibility export for existing blocks.
|
|
2
|
+
// TODO(custom-blocks): Remove the Vite server entirely when bumping bridge protocol version.
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
* self-hosted `custom_blocks.json` manifest to the custom-block bundle so the
|
|
4
|
-
* SDK's runtime fetch can resolve.
|
|
5
|
-
*
|
|
6
|
-
* Authored as plain JS (with `index.d.ts` co-located for types) — the rest of
|
|
7
|
-
* the SDK is `.ts` consumed directly via the workspace symlink, but Vite's
|
|
8
|
-
* config loader resolves this subpath through Node's ESM resolver, which
|
|
9
|
-
* can't load `.ts` source files. Lives in its own `vite-plugin/` directory
|
|
10
|
-
* so it can grow its own tsconfig later.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
*
|
|
14
|
-
* import { notionCustomBlock } from "@notionhq/custom-blocks/vite"
|
|
15
|
-
*
|
|
16
|
-
* export default defineConfig({
|
|
17
|
-
* plugins: [react(), notionCustomBlock()],
|
|
18
|
-
* })
|
|
19
|
-
*
|
|
20
|
-
* Requires `"type": "module"` in the consuming project's `package.json` so
|
|
21
|
-
* Vite's config loader uses Node's ESM resolver (not CommonJS `require`,
|
|
22
|
-
* which fails for ESM-only deps).
|
|
23
|
-
*
|
|
24
|
-
* - **Dev (`vite`)** — middleware serves `/custom_blocks.json` from the project root
|
|
25
|
-
* when the optional file exists.
|
|
26
|
-
* - **Build (`vite build`)** — emits an existing `custom_blocks.json` as an asset
|
|
27
|
-
* alongside the built `index.html` so `vite preview` and any local file server
|
|
28
|
-
* can serve it.
|
|
29
|
-
*
|
|
30
|
-
* Validation is intentionally not performed here — the SDK validates on
|
|
31
|
-
* receipt, and the CLI validates separately at deploy time.
|
|
4
|
+
* @deprecated This compatibility stub does nothing. Remove it from Vite config when possible.
|
|
32
5
|
*/
|
|
33
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
34
|
-
import { resolve } from "node:path"
|
|
35
|
-
|
|
36
|
-
const MANIFEST_FILENAME = "custom_blocks.json"
|
|
37
|
-
const MANIFEST_URL_PATH = `/${MANIFEST_FILENAME}`
|
|
38
|
-
const REQUIRED_BASE = "./"
|
|
39
|
-
|
|
40
6
|
export function notionCustomBlock() {
|
|
41
|
-
|
|
42
|
-
let command = "serve"
|
|
43
|
-
|
|
44
|
-
function manifestPath() {
|
|
45
|
-
return resolve(projectRoot, MANIFEST_FILENAME)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readManifest() {
|
|
49
|
-
const file = manifestPath()
|
|
50
|
-
if (!existsSync(file)) {
|
|
51
|
-
return undefined
|
|
52
|
-
}
|
|
53
|
-
return readFileSync(file, "utf8")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function middleware(req, res, next) {
|
|
57
|
-
const path = req.url?.split("?", 1)[0]
|
|
58
|
-
if (path !== MANIFEST_URL_PATH) {
|
|
59
|
-
next()
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
const contents = readManifest()
|
|
63
|
-
if (contents === undefined) {
|
|
64
|
-
res.statusCode = 404
|
|
65
|
-
res.end()
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
res.setHeader("Content-Type", "application/json")
|
|
69
|
-
res.end(contents)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
name: "custom-blocks:notion-manifest",
|
|
74
|
-
config(userConfig) {
|
|
75
|
-
// Custom blocks are served from a content-addressed path that
|
|
76
|
-
// has trailing slashes, eg https://dev.notion.so/custom-block-bundle/357b35e6-e67f-81fd-b425-00e7b3e2afd1/
|
|
77
|
-
// so all asset URLs in the built bundle must be relative.
|
|
78
|
-
// Force `base: "./"` and reject any other explicit value
|
|
79
|
-
//
|
|
80
|
-
// Note: once we are set up with a wildcard subdomain host, this will no longer be necessary
|
|
81
|
-
// and should be removed, since /assets will work fine.
|
|
82
|
-
if (userConfig.base !== undefined && userConfig.base !== REQUIRED_BASE) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`@notionhq/custom-blocks/vite: \`base\` must be "${REQUIRED_BASE}" (got ${JSON.stringify(userConfig.base)}). Custom blocks need relative asset paths so the bundle works under any host-served prefix.`,
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
return { base: REQUIRED_BASE }
|
|
88
|
-
},
|
|
89
|
-
configResolved(config) {
|
|
90
|
-
projectRoot = config.root
|
|
91
|
-
command = config.command
|
|
92
|
-
},
|
|
93
|
-
configureServer(server) {
|
|
94
|
-
server.middlewares.use(middleware)
|
|
95
|
-
},
|
|
96
|
-
configurePreviewServer(server) {
|
|
97
|
-
server.middlewares.use(middleware)
|
|
98
|
-
},
|
|
99
|
-
buildStart() {
|
|
100
|
-
// `emitFile` is only valid during `vite build`. In serve mode the
|
|
101
|
-
// middleware above handles `/custom_blocks.json`, so we skip emission.
|
|
102
|
-
if (command !== "build") {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
const contents = readManifest()
|
|
106
|
-
if (contents === undefined) {
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
this.emitFile({
|
|
110
|
-
type: "asset",
|
|
111
|
-
fileName: MANIFEST_FILENAME,
|
|
112
|
-
source: contents,
|
|
113
|
-
})
|
|
114
|
-
},
|
|
115
|
-
}
|
|
7
|
+
return { name: "custom-blocks:legacy-vite-plugin" }
|
|
116
8
|
}
|
package/docs/vite-plugin.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Vite plugin
|
|
2
|
-
|
|
3
|
-
The `notionCustomBlock()` plugin from `@notionhq/custom-blocks/vite` wires a block's local dev server and build output to the SDK handshake.
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
import { defineConfig } from "vite";
|
|
7
|
-
import react from "@vitejs/plugin-react";
|
|
8
|
-
import { notionCustomBlock } from "@notionhq/custom-blocks/vite";
|
|
9
|
-
|
|
10
|
-
export default defineConfig({
|
|
11
|
-
plugins: [react(), notionCustomBlock()],
|
|
12
|
-
});
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
It does two things:
|
|
16
|
-
|
|
17
|
-
- **Forces `base: "./"`** (and rejects any other explicit value). Custom blocks are served from a content-addressed path, so asset URLs in the built bundle must be relative.
|
|
18
|
-
- **Passes through `custom_blocks.json`** when the optional file exists in the project root: served by middleware in dev so HMR and the SDK handshake see the same file, and emitted into `dist/` as a separate asset on `vite build`.
|
|
19
|
-
|
|
20
|
-
The `custom_blocks.json` manifest file only applies when a block is served from `localhost` during development instead of deployed as a worker. Worker-backed blocks declare their manifest with `worker.customBlock(...)` and should not maintain a `custom_blocks.json`, but they still use the plugin for the `base` enforcement. See [deployment.md](./deployment.md) for more information.
|
|
21
|
-
|
|
22
|
-
The plugin does not validate the manifest. The SDK validates it on receipt.
|