@notionhq/custom-blocks-dev-shell 0.1.66 → 0.1.67

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/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Workers local shell</title>
7
- <script type="module" crossorigin src="/assets/index-BtBedBtp.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-jvcNWLYF.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BOt07-3a.css">
9
9
  </head>
10
10
  <body>
@@ -14,6 +14,8 @@ import { formatUnknownErrorWithStack } from "./errors.js";
14
14
  import { materializeWorkerSchemaDataSources } from "./materialize.js";
15
15
  import { BLOCK_BASE_PORT, parsePort, SHELL_PORT, validateBlockPortRange, validateShellPort, } from "./ports.js";
16
16
  import { installProcessSignalHandlers, ProcessSupervisor, } from "./process-supervisor.js";
17
+ import { resolveLaunchBindings } from "./resolve-bindings.js";
18
+ import { parseBindingOverride, } from "./source-bindings.js";
17
19
  import { parseCliArgs } from "./utils.js";
18
20
  import { writeBlockViteConfig } from "./vite-block-server.js";
19
21
  import { blockCapabilities, findWorkerDir, generateWorkerManifest, } from "./worker-manifest.js";
@@ -59,11 +61,15 @@ export function parseWorkerLaunchArgs(argv) {
59
61
  worker: undefined,
60
62
  shellPort: SHELL_PORT,
61
63
  blockBasePort: BLOCK_BASE_PORT,
64
+ bindings: [],
62
65
  };
63
66
  let hasPositionalWorker = false;
64
67
  parseCliArgs({
65
68
  argv,
66
69
  handlers: {
70
+ "--bind": value => {
71
+ args.bindings.push(parseBindingOverride(value));
72
+ },
67
73
  "--worker": value => {
68
74
  if (value.length === 0) {
69
75
  throw new Error("--worker requires a path to a worker directory.");
@@ -83,7 +89,7 @@ export function parseWorkerLaunchArgs(argv) {
83
89
  },
84
90
  onUnknown: arg => {
85
91
  throw new Error(`Unknown dev shell option "${arg}". Supported: --worker <dir>, ` +
86
- `--port <port>, --block-base-port <port>.`);
92
+ `--port <port>, --block-base-port <port>, --bind <capabilityKey>.<dataSourceKey>=<sampleDataFileName>.`);
87
93
  },
88
94
  onPositional: arg => {
89
95
  // The published launcher passes its optional worker path positionally.
@@ -186,6 +192,13 @@ async function prepareWorkerLaunch(options) {
186
192
  const plan = await prepareWorkerLaunchPlan(workerDir, {
187
193
  blockBasePort: args.blockBasePort,
188
194
  });
195
+ const { registryBindings } = resolveLaunchBindings(plan.blocks.map(block => block.capability), plan.dataSources, args.bindings);
196
+ for (const entry of plan.registry) {
197
+ const bindings = registryBindings.get(entry.key);
198
+ if (bindings !== undefined) {
199
+ entry.sampleFilesByDataSource = bindings;
200
+ }
201
+ }
189
202
  logWorkerPlan(workerName, plan);
190
203
  validateShellPort(args.shellPort, args.blockBasePort, plan.blocks.length);
191
204
  const shell = await options.createShellLaunch({
@@ -0,0 +1,39 @@
1
+ /** Validate explicit file selections before servers start. */
2
+ export function resolveLaunchBindings(blocks, sources, overrides) {
3
+ const targets = blocks.flatMap(block => Object.entries(block.config.manifest.dataSources).map(([slotKey]) => ({
4
+ blockKey: block.key,
5
+ slotKey,
6
+ target: `${block.key}.${slotKey}`,
7
+ })));
8
+ const explicit = validateOverrides(targets.map(candidate => candidate.target), sources, overrides);
9
+ const registryBindings = new Map();
10
+ for (const { blockKey, slotKey, target } of targets) {
11
+ const source = explicit.get(target);
12
+ if (source === undefined) {
13
+ continue;
14
+ }
15
+ const bindings = registryBindings.get(blockKey) ??
16
+ Object.create(null);
17
+ bindings[slotKey] = `${source.filename}.json`;
18
+ registryBindings.set(blockKey, bindings);
19
+ }
20
+ return { registryBindings };
21
+ }
22
+ function validateOverrides(targets, sources, overrides) {
23
+ const explicit = new Map();
24
+ for (const { target, filename } of overrides) {
25
+ const matches = targets.filter(candidate => candidate === target);
26
+ if (matches.length !== 1) {
27
+ throw new Error(`${matches.length === 0 ? "Unknown" : "Ambiguous"} binding target "${target}". Available targets: ${targets.join(", ") || "(none)"}.`);
28
+ }
29
+ if (explicit.has(target)) {
30
+ throw new Error(`Duplicate --bind target "${target}". Specify each target once.`);
31
+ }
32
+ const source = sources.find(candidate => `${candidate.filename}.json` === filename);
33
+ if (source === undefined) {
34
+ throw new Error(`Cannot bind ${target}: data/${filename} does not exist. Available files: ${sources.map(candidate => `${candidate.filename}.json`).join(", ") || "(none)"}.`);
35
+ }
36
+ explicit.set(target, source);
37
+ }
38
+ return explicit;
39
+ }
@@ -0,0 +1,17 @@
1
+ export function parseBindingOverride(value) {
2
+ const separator = value.indexOf("=");
3
+ const target = value.slice(0, separator);
4
+ const filename = value.slice(separator + 1);
5
+ const dot = target.indexOf(".");
6
+ if (separator < 0 ||
7
+ dot <= 0 ||
8
+ dot === target.length - 1 ||
9
+ target !== target.trim() ||
10
+ filename !== filename.trim() ||
11
+ !filename.endsWith(".json") ||
12
+ filename === ".json" ||
13
+ /[\\/\0]/.test(filename)) {
14
+ throw new Error("--bind requires <capabilityKey>.<dataSourceKey>=<sampleDataFileName.json>. Use a filename in data/.");
15
+ }
16
+ return { target, filename };
17
+ }
package/docs/bindings.md CHANGED
@@ -1,36 +1,60 @@
1
- # Dev-shell bindings
1
+ # Dev shell bindings
2
2
 
3
- The shell lists your worker's blocks in the sidebar and renders the selected
4
- block in a mock Notion host. Blocks start fully unbound: nothing renders
5
- until every data source slot the block declares is connected to a source and
6
- every declared property is mapped.
3
+ The dev shell previews your worker's custom blocks with local data from
4
+ `data/*.json`. Each data source selects its file in this order:
5
+
6
+ 1. The explicit `--bind` selection.
7
+ 2. `data/worker_<dataSourceKey>.json`, if it exists.
8
+ 3. `data/<dataSourceKey>.json`, if it exists.
9
+ 4. Otherwise, it stays unbound. The shell starts, but the block cannot initialize
10
+ until you connect the source in **Data**.
11
+
12
+ An invalid explicit selection stops startup instead of falling back to another
13
+ file. An empty file still takes precedence over later choices.
14
+
15
+ ## Choose sample data
16
+
17
+ Use `--bind <capabilityKey>.<dataSourceKey>=<sampleDataFileName>` to choose a source:
18
+
19
+ ```bash
20
+ ntn workers customblocks dev -- --bind task-board.tasks=sample_tasks.json
21
+ ```
22
+
23
+ This connects the `tasks` data source on `task-board` to `data/sample_tasks.json`.
24
+ Use a filename from `data/`, including `.json`.
25
+
26
+ Repeat `--bind` for additional data sources. Sources without an override keep
27
+ their defaults. When running `custom-blocks-dev-shell` directly, omit the `--`
28
+ separator.
29
+
30
+ An unknown target, duplicate target, or missing file stops startup.
31
+ Invalid JSON or an invalid source file also stops startup.
32
+
33
+ The browser maps compatible properties automatically. Incomplete mappings do
34
+ not stop the shell. Open **Data** to finish connecting the block.
35
+ Empty rows are valid.
7
36
 
8
37
  ## Connecting data
9
38
 
10
- **Connect data** in the toolbar opens the bindings modal: one section
39
+ **Data** in the toolbar opens the bindings modal: one section
11
40
  per slot from the block's `worker.customBlock({ dataSources })` declaration,
12
41
  each with a source picker and a per-property mapping. The sources on offer
13
42
  are the ones in your worker's `data/*.json`
14
- (format: [`data-sources.md`](./data-sources.md)); the toolbar shows a chip
15
- per slot with its current binding state.
43
+ (format: [`data-sources.md`](./data-sources.md)). The modal shows each slot
44
+ and its property mappings.
16
45
 
17
- Picking a source auto-maps its properties. For each manifest property, only
18
- schema properties of the same type are candidates. A property is a candidate
19
- on an exact ID/key match (no trim or case folding) or a display name match
20
- after trim and lowercasing. Bind when there is exactly one candidate;
21
- anything ambiguous or absent stays unmapped. Re-picking a source resets the
22
- slot's mappings and re-runs auto-map. Manual mapping offers only source
23
- properties of exactly the required type — no coercion.
46
+ Properties map automatically when there is a unique source property with a
47
+ matching key or name and the same type. Use the property pickers to adjust
48
+ mappings. Selecting a different source resets its property mappings.
24
49
 
25
50
  ## Initialization & binding edits
26
51
 
27
52
  Once every slot is bound and every property mapped, the block initializes and
28
- renders. Any binding edit restarts the block: the shell recreates the block's
29
- iframe, and the fresh initialization carries the new bindings. A block that
30
- failed to initialize recovers the same way — fix its bindings and save.
53
+ renders. Saving binding changes restarts the block with the updated data.
54
+ If initialization fails, fix the bindings and save.
31
55
 
32
56
  Bindings last for the page load, per block: refresh the shell and every block
33
- returns to unbound.
57
+ returns to its CLI selection or filename default.
34
58
 
35
59
  The shell reads `data/*.json` once at spin-up. To pick up file edits,
36
60
  restart the dev-shell command; a browser refresh is not enough.
@@ -11,6 +11,8 @@ supports the following types today:
11
11
 
12
12
  ## File format
13
13
 
14
+ For example, `data/tasks.json`:
15
+
14
16
  ```json
15
17
  {
16
18
  "type": "worker",
@@ -92,3 +94,21 @@ schema with empty `rows`, mirroring a managed database before any sync.
92
94
  schema.
93
95
 
94
96
  The shell shows exactly the sources in `data/`, nothing else.
97
+
98
+ ## Sample rows into a generated source
99
+
100
+ To populate a declared slot, use the generated file as the sample target:
101
+
102
+ ```bash
103
+ ntn workers customblocks sample <url-or-data-source-id> \
104
+ --out data/worker_tasks.json --row-only
105
+ ```
106
+
107
+ Run the dev shell once to create the generated file, then stop it before sampling.
108
+ `--row-only` preserves the schema and appends rows with new IDs. Add `--overwrite`
109
+ to replace existing rows.
110
+
111
+ Sampled property keys must match the generated schema. If they differ, sample
112
+ into a separate file and select it with [`--bind`](./bindings.md#choose-sample-data).
113
+
114
+ Restart the dev shell after sampling to load the updated data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/custom-blocks-dev-shell",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "description": "Local preview shell for Notion custom block workers.",
5
5
  "license": "MIT",
6
6
  "type": "module",