@protobase/cli 0.67.10 → 0.67.11

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.
@@ -19,19 +19,30 @@ const isVendorModule = (id) => {
19
19
  const name = npmPackage(id);
20
20
  return name !== void 0 && !reactPackages.has(name) && name !== "lucide-react";
21
21
  };
22
+ const preloadHelper = "\0vite/preload-helper.js";
23
+ /** The chunk name of a third-party module only lazy code loads: its package's, such as `lib-xyflow-react`. */
24
+ const libChunkName = (id) => `lib-${npmPackage(id).replace("@", "").replace("/", "-")}`;
22
25
  /**
23
26
  * The shell build's `output.codeSplitting`. `react` comes first: without it, `vendor` would take React in as a
24
27
  * dependency of the libraries it holds, and lazy chunks would import it from there.
25
28
  */
26
- const shellCodeSplitting = { groups: [{
27
- name: "react",
28
- test: isReactModule,
29
- priority: 2
30
- }, {
31
- name: "vendor",
32
- test: isVendorModule,
33
- tags: ["$initial"],
34
- priority: 1
35
- }] };
29
+ const shellCodeSplitting = { groups: [
30
+ {
31
+ name: "react",
32
+ test: isReactModule,
33
+ priority: 2
34
+ },
35
+ {
36
+ name: "vendor",
37
+ test: (id) => isVendorModule(id) || id === preloadHelper,
38
+ tags: ["$initial"],
39
+ priority: 1
40
+ },
41
+ {
42
+ name: libChunkName,
43
+ debugName: "lib",
44
+ test: isVendorModule
45
+ }
46
+ ] };
36
47
  //#endregion
37
- export { isReactModule, isVendorModule, npmPackage, shellCodeSplitting };
48
+ export { isReactModule, isVendorModule, libChunkName, npmPackage, shellCodeSplitting };
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.67.10";
2
+ var version = "0.67.11";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/cli",
3
- "version": "0.67.10",
3
+ "version": "0.67.11",
4
4
  "description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,12 +43,12 @@
43
43
  "dependencies": {
44
44
  "@better-auth/core": "^1.7.12",
45
45
  "@hono/node-server": "^2.1.3",
46
- "@protobase/config": "0.67.10",
47
- "@protobase/layout": "0.67.10",
48
- "@protobase/query": "0.67.10",
49
- "@protobase/schema": "0.67.10",
50
- "@protobase/server": "0.67.10",
51
- "@protobase/ui": "0.67.10",
46
+ "@protobase/config": "0.67.11",
47
+ "@protobase/layout": "0.67.11",
48
+ "@protobase/query": "0.67.11",
49
+ "@protobase/schema": "0.67.11",
50
+ "@protobase/server": "0.67.11",
51
+ "@protobase/ui": "0.67.11",
52
52
  "@react-email/render": "^2.1.0",
53
53
  "@scalar/hono-api-reference": "^0.12.10",
54
54
  "@sentry/vite-plugin": "^11.10.0",
@@ -6,7 +6,8 @@ Graphs of steps the server runs along their edges, written in a language a model
6
6
 
7
7
  - [Workflows](../reference/workflows.md#turning-them-on) (beta): Workflows kept in the app's database, each changed definition checked and kept as a version, run on the app's own jobs.
8
8
  - [Workflow language](../reference/workflows.md#the-document) (beta): The States Language with JSONata expressions, every value read by name, so a model writes and edits it as a small, local change.
9
- - [Checked on save](../reference/workflows.md#reading-values-by-name) (beta): A definition that does not fit the language, has an edge to no step or reads a value nothing gives is refused, naming the step and the field.
9
+ - [Checked on save](../reference/workflows.md#reading-values-by-name) (beta): A definition that does not fit the language, has an edge to no step, reads a value nothing gives or leaves out an argument its step type needs is refused, naming the step and the field.
10
+ - [Workflow editor](../reference/workflows.md#the-editor) (beta): The admin app draws a workflow as a graph that lays itself out, to add, connect and remove steps and fill in each step's form, with every problem the save would refuse shown on its step as it is edited.
10
11
  - [Branches, steps per item and retries](../reference/workflows.md#states) (beta): Choices, a step per item with their results merged, waits, and per-step retries, timeouts and error paths.
11
12
  - [HTTP request step](../reference/workflows.md#apphttp) (beta): Calls a URL built at run time through the outgoing-address guard, only at public addresses unless the config allows private ones, reading at most 10 MiB of the answer as JSON, text or a file.
12
13
  - [Records steps](../reference/workflows.md#apprecords) (beta): Create, update, get and list records as the workflow's last editor, with the roles they hold now.
@@ -233,8 +233,8 @@ A component that asks for input looks like the app's own forms when it is built
233
233
  | --- | --- |
234
234
  | `Form` | The create form's frame: `submit`, the button's label; `onSubmit`; `saving` and `disabled` for the button; `onCancel`, which adds Cancel; and `error` with `errorTitle`, a failure that belongs to no field, where an `ApiError` shows its messages. With `title` (and `eyebrow`, the line above it) the form is the whole page, its buttons beside the heading, as the create form is. Without, it sits in a composed page under the page's heading, its buttons after the sections. |
235
235
  | `Section` | A titled part of a form or page: `title`, `help`, `actions`. Sections in a `Form` have a line between them. |
236
- | `Field` | A label over its input: `label`, `required`, `help`, `example`, `error`, `name` for the browser's autofill, and `wide` for both columns of a grid of two. An `Input` or `Textarea` inside takes its label, name, help and error from it, and whether it is invalid or required; another control spreads `useFieldControl()` on its element. |
237
- | `Input`, `Textarea` | A text box, with `leading` and `trailing` content and `invalid`; a box of several lines that grows with its text. |
236
+ | `Field` | A label over its input: `label`, `required`, `help`, `example`, `error`, `name` for the browser's autofill, and `wide` for both columns of a grid of two. An `Input`, `Textarea` or `Select` inside takes its label, name, help and error from it, and whether it is invalid or required; another control spreads `useFieldControl()` on its element. |
237
+ | `Input`, `Textarea`, `Select` | A text box, with `leading` and `trailing` content and `invalid`; a box of several lines that grows with its text; a native select with `invalid`. |
238
238
  | `Choices` | One of a few options, each a card in a row of its own with a `description`: `name`, `label`, `options`, `value`, `onChange`. A `disabled` option shows and cannot be chosen. |
239
239
  | `RecordPicker` | Records of another resource, picked as the create form picks a relation: type to narrow them by their search fields or title, scroll for more, or use the keyboard. `resource` names the resource, whose model it reads from the app; `value` is the chosen record's key as text and `onChange` gets the new one. With `multiple`, `value` and `onChange` hold several keys, such as the objectives a task serves through a join resource: each shows as a token that can be taken off. |
240
240
  | `Segmented`, `MenuSelect` | A switch between a few short options in a row (`size`, `iconOnly`), and the same options in a menu. |
@@ -163,6 +163,10 @@ view<typeof channels>('channels').fields((r) => ({ member_ids: r.member_ids.widg
163
163
 
164
164
  The create form and the record's form pick the members of the organization the session works in by name: typing narrows them, each choice is a token whose button takes it off again, and choosing a chosen member takes them off too. It stores their user ids, the ids `.author()` stores and [`useMembers()`](custom-components.md#members-names) gives, never membership ids. The record page, cards and the list's table show each member by avatar and name; an id that is no longer a member shows as an unknown member. A JSON field is on a form only through a field type that edits it, so without the hint `member_ids` stays read-only. The field needs [organizations](auth.md#organizations); in an app without them it says so in place of the members. <gap>The members picker cannot search on the server because a server-side people resource is not implemented, so it loads the whole list in the browser, as `useMembers()` does.</gap> A plugin that registers a field type named `members` replaces it, and the server refuses to start when a view picks it for a field that is no JSON.
165
165
 
166
+ ### Workflow
167
+
168
+ `workflow` is the field type the `workflows` resource draws its `definition` with: the [workflow editor](workflows.md#the-editor). Another JSON field holding a workflow's definition picks it as `r.definition.widget('workflow')`. It takes the form's whole width, and a list's table shows how many steps the workflow has. A plugin that registers a field type named `workflow` replaces it.
169
+
166
170
  ## Building
167
171
 
168
172
  ```sh
@@ -5,7 +5,7 @@
5
5
  Workflows written as state machines in the States Language with JSONata, checked on every save, run step by step on the app's jobs, with every step recorded.
6
6
 
7
7
  :::caution[Beta]
8
- Workflows run when they are started through the API, over MCP or from the app's code. Triggers, sign-in connections to other services and a graph editor are not built yet.
8
+ Workflows run when they are started through the API, over MCP or from the app's code. Triggers and sign-in connections to other services are not built yet.
9
9
  :::
10
10
 
11
11
  A **workflow** is a graph of steps that the server runs one after another along its edges: fetch something, decide, run a step per item, merge the results, write records, send mail. It is written in the **States Language**, the JSON language of state machines, with **JSONata** as its expression language, in a profile of it that reads every value by name. A language model writes and changes it as easily as a person, and the same document is what the server checks and runs. Each run records every step it takes, with what the step read, gave and logged.
@@ -79,7 +79,7 @@ A definition is one JSON document: the workflow's trigger, and the state machine
79
79
  }
80
80
  ```
81
81
 
82
- The document holds no layout: an editor lays the graph out from its edges.
82
+ The document holds no layout: the [editor](#the-editor) lays the graph out from its edges.
83
83
 
84
84
  ### Triggers
85
85
 
@@ -141,6 +141,19 @@ A state's output is its `Output`, else its result (a Task or Map) or the input i
141
141
 
142
142
  A failing Task or Map fails with an error name: a step type's own, such as `Http.Status`, `States.Timeout` when it ran past its timeout, `States.TaskFailed` for any other error it threw, and `States.QueryEvaluationError` for an expression that did not evaluate or ran past its limits. The first retrier whose `ErrorEquals` names it tries the state again after `IntervalSeconds` (default 1), times `BackoffRate` (default 2) for each earlier retry, up to `MaxDelaySeconds`; `MaxAttempts` counts the retries after the first try (default 3, 0 for none). Without `Retry` a failure is not retried. Once no retry is left, the first catcher that names it goes on to its `Next`; without one the run fails. `States.ALL` names every error but `States.DataLimitExceeded` and `States.Runtime`, which end the run even where a retrier or catcher names them.
143
143
 
144
+ ## The editor
145
+
146
+ A workflow's record page in the admin app, `/workflows/<id>`, and its create form draw the definition as a graph to edit, and save the same document the API saves:
147
+
148
+ - **The graph lays itself out.** The trigger is on top, each state is a box, a Map is a box around the states it runs for each item, and each edge is drawn from the field that names it: a state's `Next`, a Choice's rules (labelled with their condition) and its `Default` ("Otherwise"), and a Task's or Map's catchers ("On error", dashed). The graph is laid out by ELK's layered algorithm, from top to bottom, each time a state or edge is added, removed or renamed; the document keeps no positions, so the graph a person sees is the one a model or the API wrote, and the one they edit is what a model reads back. The page scrolls under the wheel; the canvas zooms with its buttons and by pinching.
149
+ - **Adding a step.** *Add a step* adds a Task of a step type, or a Choice, Map, Wait, Pass, Succeed or Fail, named `Step 1`, `Step 2` and so on. It goes into the Map chosen, or the Map the chosen step is in, else into the workflow; it ends the run, or its item, until it is connected, and in a workflow or Map without steps it is the first.
150
+ - **Connecting steps.** Drag from a step's handle to another step, or tap one handle and then the other: the bottom handle sets `Next`, a Choice's *When* handle adds a rule and its *Otherwise* handle sets `Default`, a Task's or Map's *On error* handle adds a catcher of `States.ALL`, and the trigger's handle sets `StartAt`. A step leads only to steps of its own machine, so an edge into or out of a Map is refused. The chosen step's form sets the same edges with menus: *Goes on to*, each rule's and catcher's *Then goes to*, and *Otherwise goes to*.
151
+ - **Removing.** Delete or Backspace removes the chosen step or edge, as does the form's *Remove step*. Removing a step removes the edges to it: a `Next` to it becomes `End`, and a rule, `Default` or catcher naming it goes. Removing the first step of a workflow or Map makes the first step left in it the first.
152
+ - **A step's form.** Choosing a step opens its form under the graph: its name, which renames every edge to it, then what its type takes. A Task's form has a field for each argument of its step type, the ones it needs marked, as listed under [Step types](#step-types); a Choice's its rules and `Default`; a Map's `Items` and `MaxConcurrency`; a Wait's `Seconds`; a Pass's and a Succeed's `Output`; a Fail's `Error` and `Cause`. A field takes text, an expression (`{% ... %}`) or JSON; an emptied field is left out. *The step as JSON* edits every field of the step, such as `Retry`, `Assign` and `TimeoutSeconds`. The trigger's form picks the trigger and its schedule, mailbox or events.
153
+ - **Problems on their step.** The editor runs the same check as a save on every change, and shows each problem on the step it names, as a count on its box and a list and a marked field in its form; a problem with the trigger or the first step shows on the trigger, and one about the whole document above the graph. Saving writes the definition through the API, where the save hook checks it again: a definition with a problem is refused with the 400 above, whose `errors` hold each problem for the `definition` field, with its state and field in `params`.
154
+
155
+ A save also refuses a Task that leaves out an argument its step type needs, such as `app:http` without `url`, unless its `Arguments` is one expression, whose value is known only when it runs. A user who may read a workflow but not change it sees the same graph and forms, locked. <gap>The editor cannot undo a single change because an undo history is not implemented.</gap>
156
+
144
157
  ## Step types
145
158
 
146
159
  ### app:http
@@ -191,7 +204,7 @@ A file value is `{ uri, name, type, size }`, the handle to bytes in the app's st
191
204
 
192
205
  A run starts when it is created, through the API or the admin app, in the same transaction: `POST /api/v1/workflow_runs` with `{ "workflow_id": "...", "input": { ... } }`. The input is at most 1 MiB as JSON and nests at most 64 levels; a larger one is refused with `input-too-large` or `input-too-deep` and no run. From the app's own code, such as a job or a write hook, `startWorkflow(db, { workflow, input, tenant, startedBy })` from `@protobase/server` starts one in `db`'s transaction, so it starts when the caller's writes commit, or not at all. With a `key`, such as `orders:42`, a workflow runs once per key: a start with a key it has a run for already, in this transaction or another, starts nothing and returns that run's id.
193
206
 
194
- Each step is saved as it ends, as a `workflow_steps` record: the state, its type, the scope it ran in (empty for the run itself, `Each.3[0]` for item 0 of the Map `Each` that was step 3), its number, its try, its status (`succeeded`, `retrying`, `caught` or `failed`), its input, output and assigned variables, its logs, when it started and ended and how long it took, its error and cause, and where it goes on to. A run's status is `queued`, `running`, `waiting` (with the time it goes on), `succeeded` with its output, or `failed` with its error and cause.
207
+ Each step is recorded as a `workflow_steps` record: the state, its type, the scope it ran in (empty for the run itself, `Each.3[0]` for item 0 of the Map `Each` that was step 3), its number, its try, its status (`succeeded`, `retrying`, `caught` or `failed`), its input, output and assigned variables, its logs, when it started and ended and how long it took, its error and cause, and where it goes on to. A run's status is `queued`, `running`, `waiting` (with the time it goes on), `succeeded` with its output, or `failed` with its error and cause.
195
208
 
196
209
  ### Secrets
197
210
 
@@ -225,8 +238,8 @@ All four are scoped to the tenant like any resource with `.tenant()`. A run take
225
238
  The runner is `@protobase/workflows`, a package with no other Protobase dependency, over a narrow port: where runs and steps are kept (here the `workflows` schema), the queue that runs them (here the app's [jobs](jobs.md)), and the step types.
226
239
 
227
240
  - A run is run in **segments**, each a `workflows.segment` job whose payload is the run's id alone: one runs the run from where it stands to its next wait, then queues the next. A Wait whose time is still ahead, or a retry's delay, saves its step with the time the run goes on, ends the segment and queues the next one for that time, so a waiting run holds no worker. A segment hands the rest of the run on after a minute of steps.
228
- - Where a run stands is read from its saved steps. Each step is saved with **one statement**, unique by its scope and number, so a second segment of the same run saves nothing and stops.
229
- - A worker that stops or dies mid-step leaves that step unsaved: its job is tried again and takes the run up from its last saved step, so a saved step never runs again, and the step that was in progress runs **at least once**. A step whose effect happened before its result was saved, such as a request that was sent, happens again. Every try of a step gets the same idempotency key, for a step type whose receiver dedupes by it, and `app:mail.send` makes it the mail's Message-ID. A job whose worker died is tried again once its timeout, 10 minutes, has passed.
241
+ - Where a run stands is read from its saved steps. A Task's step, and one with a time the run goes on at, is saved before the next state runs; the steps of other states are kept and saved with it, or as the segment ends. Each save is **one statement**, unique by each step's scope and number, so a second segment of the same run that finds one taken stops.
242
+ - A worker that stops or dies mid-step leaves that step unsaved: its job is tried again and takes the run up from its last saved step, so a saved step never runs again, and the step that was in progress runs **at least once**, as do the steps kept since the last save, which give the same records again. A step whose effect happened before its result was saved, such as a request that was sent, happens again. Every try of a step gets the same idempotency key, for a step type whose receiver dedupes by it, and `app:mail.send` makes it the mail's Message-ID. A job whose worker died is tried again once its timeout, 10 minutes, has passed.
230
243
  - A Map's items run in the segment that reaches them, each item's steps saved under its own scope; an item that waits ends the segment once the others running settle, and the next segment takes each item up where it stands. A Map's `Items` is evaluated again by a segment that takes up an unfinished Map.
231
244
  - JSONata runs in the server's process under a time and a depth limit per expression; it reaches no file, network or code of the app.
232
245