@protobase/cli 0.68.0 → 0.68.2

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.
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.68.0";
2
+ var version = "0.68.2";
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.68.0",
3
+ "version": "0.68.2",
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,13 +43,13 @@
43
43
  "dependencies": {
44
44
  "@better-auth/core": "^1.7.12",
45
45
  "@hono/node-server": "^2.1.3",
46
- "@protobase/config": "0.68.0",
47
- "@protobase/layout": "0.68.0",
48
- "@protobase/query": "0.68.0",
49
- "@protobase/schema": "0.68.0",
50
- "@protobase/server": "0.68.0",
51
- "@protobase/ui": "0.68.0",
52
- "@protobase/workflows": "0.68.0",
46
+ "@protobase/config": "0.68.2",
47
+ "@protobase/layout": "0.68.2",
48
+ "@protobase/query": "0.68.2",
49
+ "@protobase/schema": "0.68.2",
50
+ "@protobase/server": "0.68.2",
51
+ "@protobase/ui": "0.68.2",
52
+ "@protobase/workflows": "0.68.2",
53
53
  "@react-email/render": "^2.1.0",
54
54
  "@scalar/hono-api-reference": "^0.12.10",
55
55
  "@sentry/vite-plugin": "^11.10.0",
@@ -9,3 +9,4 @@ Buttons that change records, ask for input or run your own code, per role.
9
9
  - [Conditional and bulk actions](../reference/ui-config.md#where-an-action-applies): Actions show, or show disabled with a reason, only where a condition holds, and run on several selected rows at once.
10
10
  - [Views per role](../reference/ui-config.md#actions-and-views-per-role): Each role can get its own view of a resource, with other columns, filters, layout and actions.
11
11
  - [Named actions](../reference/layouts.md#named-actions): An action is declared once in a view and used as a button on lists, record pages and composed pages.
12
+ - [Links to a resource the record names](../reference/layouts.md#links-to-a-resource-the-record-names): A link action goes to the list or a record of the resource a field of the record names, and shows once the app serves it.
@@ -8,6 +8,7 @@ The sidebar, the user menu, the bottom bar on phones and the global search.
8
8
  - [Sidebar icons](../reference/sidebar.md#sidebar-icons): Each resource shows the icon its view names in the sidebar, bottom bar and profile menu.
9
9
  - [Recent records](../reference/sidebar.md#recent-records) (beta): A sidebar entry lists a few records under it, with status dots or as titles alone, and marked per reader, such as unread ones in bold with a count.
10
10
  - [Create link](../reference/sidebar.md#create-link): A sidebar entry links to its create form, such as "New message" under Direct messages, for whoever may create.
11
+ - [Entries for creators](../reference/sidebar.md#entries-for-creators): A sidebar entry shows only to those who may create its records, such as the tables list to those who may add tables.
11
12
  - [The current organization](../reference/sidebar.md#the-current-organization): A sidebar entry opens its list on the organization you work in, which you can clear to see every organization you are a member of.
12
13
  - [User menu](../reference/user-menu.md): Account pages, help links, security settings, setting a status, the theme switch and Sign out.
13
14
  - [Bottom bar](../reference/bottom-bar.md): On phones, tabs along the bottom for each role's main screens.
@@ -18,6 +18,7 @@ The page of one record, with its sections, related records, galleries, charts an
18
18
  - [Groups and tabs](../reference/ui-config.md#groups): Parts of a record page sit under headings that open on click, or in tabs, and the open ones are kept in the address.
19
19
  - [Key facts](../reference/ui-config.md#key-facts): A section shows its values side by side with icons.
20
20
  - [Confirming a change](../reference/ui-config.md#confirming-a-change): A field that is easy to set by mistake asks a question before a person changes it in a form or a list cell, and Cancel keeps its value.
21
+ - [Fields only once a record exists](../reference/ui-config.md#names-and-columns): A view leaves a field off the create form, such as a choice among records added after it, and the record page shows it as usual.
21
22
  - [Help while editing](../reference/ui-config.md#help-while-editing): Help lines meant for whoever changes a record show only on its form.
22
23
  - [Conditional sections](../reference/ui-config.md#form-and-read-view): Sections and fields show only while editing, only while reading, or only when a condition on the record holds.
23
24
  - [Galleries](../reference/ui-config.md#galleries): A record's photos show as tiles that open a full-screen lightbox, with keyboard and swipe navigation.
@@ -6,5 +6,6 @@ Tables an app's users add from the admin UI, by hand or from a file, and how a d
6
6
 
7
7
  - [Tables defined in the database](../reference/user-tables.md#tables-defined-in-the-database) (beta): An app can keep tables its users add in its own database, beside the resources in its code. Each one is served like any other resource, at its own address with its list, record page, search and API. Every server process serves a new table within a second of it being added.
8
8
  - [Adding tables and fields](../reference/user-tables.md#adding-tables-and-fields) (beta): Someone with the right capability creates a table with its fields, or adds a field to it, through the app's API. It is created in the database at once, with an index for every field but JSON so its list can be filtered by it, and sorted by it unless it is long text, and only admins can see it until access is widened.
9
+ - [Adding a table in the admin app](../reference/user-tables.md#in-the-admin-app) (beta): People who may add tables find Tables in the admin app, make a table with its fields and add fields later, on a computer or a phone. The new table appears in the sidebar at once.
9
10
  - [Taking a table over as code](../reference/cli.md#eject) (beta): `protobase eject` turns a table someone added in the admin app into ordinary config and a migration in the project, so a developer can customise it like any other resource. Its records stay where they are.
10
11
  - [Tables per organization](../reference/user-tables.md#organizations) (beta): In an app with organizations, a table someone adds belongs to their organization. Only its owners and admins, and superusers working in it, see the table and its records.
@@ -26,6 +26,8 @@ Numbers, amounts and dates use the conventions of the language: English as writt
26
26
 
27
27
  Every built-in screen is translated: the shell with its sidebar, top bar, search, notification bell, profile menu and bottom bar; the list page with all its displays (table, cards, board, calendar, timeline and map), filters, search, sort, imports and exports; the record page with its actions, related lists, history, comments, gallery, saving, conflicts and deleting; the create page and composed pages; saved searches and favorites; the sign-in, sign-up and two-factor screens, the account and sign-in policy pages, and the organization, people and invitation pages; public forms; the assistant's dock and a record's [lines](ui-config.md#record-lines); the notice naming a plugin that is off and why; and the messages, toasts and confirmations around them. The parts `@protobase/ui` gives your own components, such as the [form parts](custom-components.md#form-parts), the [chat parts](custom-components.md#chat-parts) and the markdown editor, come in the app's language too. So are counts, relative times ("5 minutes ago"), yes, no and the other values the app writes itself, and the notices shown while the app loads.
28
28
 
29
+ The screens where people add [tables of their own](user-tables.md#in-the-admin-app) are translated too: the tables list, a table's page with its fields, and the forms that make a table and a field, with the names of the field types. So are the other lists Protobase adds beside your resources, failed jobs, activity and comments, with their record pages, sections and actions.
30
+
29
31
  The approvals an AI client asks for over [MCP](mcp.md#writes) are translated too, with their buttons and the refusal for a client that cannot ask: in the app's `locale`, or else in the language the client's request accepts.
30
32
 
31
33
  The other texts a plugin or the server writes itself, such as an error message the API sends back, show as they are written.
@@ -81,12 +81,12 @@ Record pages take the same conditions in the `when` of `l.section`, `l.related`
81
81
  | --- | --- |
82
82
  | `a.update(name, { label, set, confirm?, icon?, bulk?, inputs? })` | Updates the record with `set`, with its ETag; not offered on a record that holds those values already, unless it asks for `inputs`, whose answers it writes where `set` says `'{name}'`. |
83
83
  | `a.remove(name, { label, confirm?, icon? })` | Deletes the record (soft delete when the resource has one). |
84
- | `a.link(name, { label, to, icon? })` | Goes `to` an [address](#addresses) in the app, with `{field}` in its key, filter, order or create values replaced by the record's value. |
84
+ | `a.link(name, { label, to, icon? })` | Goes `to` an [address](#addresses) in the app, with `{field}` in its key, filter, order or create values replaced by the record's value, and its resource too when that is only `{field}`: see [links to a resource the record names](#links-to-a-resource-the-record-names). |
85
85
  | `a.link(name, { label, href, icon? })` | Opens an `href` that leaves the app (`https:`, `mailto:`), with `{field}` replaced by the record's value (percent-encoded); an `https:` or `http:` link opens in a new tab without the referrer. An `href` that is only a field, `'{website}'`, opens the URL the record holds, as it is: see [links to a record's URL](#links-to-a-records-url). A path in the app is refused when the config loads: give it as `to`. |
86
86
  | `a.ai(name, { label, prompt, writes, confirm?, icon? })` | Has the model write the field `writes` of the record from `prompt`, `{field}` replaced by the record's values, as the user, on the server; see [AI fields and actions](ai.md#ai-actions). |
87
87
  | `a.action(name, { label, confirm?, icon?, bulk?, inputs? })` | Runs the handler the app registers under `name` in [`protobase.ui.tsx`](custom-components.md#action-handlers), after asking for its [`inputs`](ui-config.md#actions-that-ask-for-input) in a dialog when it has some. |
88
88
 
89
- `icon` is one of `archive`, `arrow-right-left`, `ban`, `bath`, `bed`, `bell`, `bookmark`, `calendar`, `camera`, `copy`, `cpu`, `door-open`, `download`, `external-link`, `file-text`, `hard-drive`, `heart`, `house`, `key`, `land-plot`, `leaf`, `memory`, `network`, `play`, `plug`, `power`, `restart`, `ruler`, `send`, `server`, `stop`, `tag`, `terminal`, `upload`, `wallet` or `wrench`; another name shows a lightning bolt. With `confirm` the button asks first. Every builder also takes `when`, `otherwise` and `reason`, which limit the action to the records it [applies to](ui-config.md#where-an-action-applies), and `variant: 'danger'`, which draws it in red. The button is disabled, with the reason as its tooltip, when the user may not update or delete the record (or, for an AI action, the field it writes), when an `a.action` has no handler, or when an update, delete or AI action has no record. After an action, everything shown of that resource reloads. The record page's action rail runs the same actions the same way; there an `a.link` is a real link that looks like the rail's buttons, which the browser can preview, copy or open in a new tab, while a disabled one is a disabled button with its reason. <gap>An `<Action>` block for an `a.link` cannot be opened in a new tab or copied as a link because drawing link actions as links is not implemented for the block.</gap>
89
+ `icon` is one of `archive`, `arrow-right-left`, `ban`, `bath`, `bed`, `bell`, `bookmark`, `calendar`, `camera`, `copy`, `cpu`, `door-open`, `download`, `external-link`, `file-text`, `hard-drive`, `heart`, `house`, `key`, `land-plot`, `leaf`, `memory`, `network`, `play`, `plug`, `plus`, `power`, `restart`, `ruler`, `send`, `server`, `stop`, `table`, `tag`, `terminal`, `upload`, `wallet` or `wrench`; another name shows a lightning bolt. With `confirm` the button asks first. Every builder also takes `when`, `otherwise` and `reason`, which limit the action to the records it [applies to](ui-config.md#where-an-action-applies), and `variant: 'danger'`, which draws it in red. The button is disabled, with the reason as its tooltip, when the user may not update or delete the record (or, for an AI action, the field it writes), when an `a.action` has no handler, or when an update, delete or AI action has no record. After an action, everything shown of that resource reloads. The record page's action rail runs the same actions the same way; there an `a.link` is a real link that looks like the rail's buttons, which the browser can preview, copy or open in a new tab, while a disabled one is a disabled button with its reason. <gap>An `<Action>` block for an `a.link` cannot be opened in a new tab or copied as a link because drawing link actions as links is not implemented for the block.</gap>
90
90
 
91
91
  ### Links to a record's URL
92
92
 
@@ -98,6 +98,16 @@ a.link('source', { label: 'View on {host}', icon: 'external-link', href: '{sourc
98
98
 
99
99
  The value is used as it is, not percent-encoded, and must be an absolute `https:` or `http:` URL: on a record whose field is empty or holds anything else the button does not show. It opens in a new tab that gets neither the app's window nor its address. `{host}` in the label becomes the URL's host without `www.`, so the button above reads "View on example.com". The field must be one of the resource's, and the button shows to whoever the view grants the field, a visitor who is not signed in included; for anyone it hides the field from, the action is left out.
100
100
 
101
+ ### Links to a resource the record names
102
+
103
+ A link whose address has only a `{field}` as its `resource` goes to the resource that field names, such as a record that describes a table and links to that table's list:
104
+
105
+ ```ts
106
+ a.link('open_table', { label: 'Open table', to: { kind: 'list', resource: '{name}' } })
107
+ ```
108
+
109
+ The button shows only while the app gives the user a resource of that name: on a record whose field is empty, or names a resource the user cannot see or the app does not serve yet, it is left out, and it shows once `/meta` names it. As the resource is known only when the link is followed, such an address takes no filter, order or create values, which the server refuses when it starts; a record's `key` may still be a `{field}`.
110
+
101
111
  ## Addresses
102
112
 
103
113
  Every place in the app has an address, and the app writes and reads them in one way, so no link or component spells out a path:
@@ -31,6 +31,10 @@ view<typeof apps>('apps')
31
31
 
32
32
  `/meta` writes the session's organization into the list's opening filter, here `isNull(parent_id) AND organization_id = "<id>"`, and into the filter of its [recent records](#recent-records). Like any opening filter it applies when the address names no filter: the facet shows the organization chosen, and the person picks others or clears it, which lists every organization they are a member of. A link with its own filter replaces it, and switching organization opens the app again on the new one. A [named list](ui-config.md#more-than-one-list) takes `tenant` in its own `nav`. `createAdmin` refuses to start when the view has no `.list(...)`, or the resource has no `.tenant()` on a `.filterable()` field.
33
33
 
34
+ ## Entries for creators
35
+
36
+ `nav({ creatorsOnly: true })` shows the entry only to whoever its list offers **New**, as for the [create link](#create-link), for a list that is there to make records, such as the [tables users add](user-tables.md#in-the-admin-app). Anyone else who may read the resource still opens it at its address.
37
+
34
38
  ## Create link
35
39
 
36
40
  `nav({ create: true })` puts a link to the resource's create form under its entry, reading "New <singular>" after the view's `names`; `create: { label }` gives it its own text. A chat's direct messages, with the conversations under the entry and a link to start one:
@@ -101,6 +101,7 @@ Field hints:
101
101
  | --- | --- |
102
102
  | `.label(text)` | Instead of the name in words (`customer_id` would be "Customer" anyway). |
103
103
  | `.help(text, { when? })` | A line under the field on the record page and in forms. `when: 'editing'` shows it only while the record is edited; see [Help while editing](#help-while-editing). |
104
+ | `.notOnCreate()` | Leaves the field off the create form, for one that makes sense only once the record exists, such as a choice among records added after it: the create leaves it empty or to its default, and the record page shows it as usual. The server stops at startup on a field a create needs, one that is not optional and has no default, a boolean too. |
104
105
  | `.confirm(question)` | Asks `question` before a person changes the value in a form or a list cell, and Cancel keeps it. See [Confirming a change](#confirming-a-change). |
105
106
  | `.format(format)` | `badge` shows statuses as coloured badges, `code` sets identifiers in monospace, as the list's identifier column too, `percent` adds a % sign, and `signed` shows a whole number in a list with its sign, green above zero and red below, such as a stock change of `+12` or `−3`; it needs an integer field. On a text field, `email`, `phone` and `url` show the value as a link that writes to it (`mailto:`), calls it (`tel:`, its digits and a leading +) or opens it in a new tab (only an `http` or `https` address; anything else stays text), on the record page, in its sidebar and in the list's cells but the title's, which links to the record; forms edit it in an email, phone or web address input, which brings up the keyboard for it on a mobile screen and offers the browser's autofill. Another field type with one of them stops the server at startup. `relative` on a timestamp shows it as "5 minutes ago" or "in 2 hours" in lists and on the record page, with the exact time on hover; `absolute` and `compact` are accepted and carried in `/meta` for apps that render their own lists. <gap>The admin does not use the `absolute` and `compact` formats yet.</gap> |
106
107
  | `.multiline()` | A text of several lines, such as a description: forms edit it in a box that grows with the text, so the page scrolls rather than the box, and the record page's sections, a page's field blocks and a card's subtitle keep its line breaks (a card shows the first three lines); the record's sidebar shows it on one line. On the record page and the create form it takes the whole width of its section. Only a text field takes it; the list's table shows the text on one line. |
@@ -793,7 +794,7 @@ view<typeof listings>('listings')
793
794
  ])
794
795
  ```
795
796
 
796
- The read view shows the gallery; the form shows the cover photo's field and the table of photos to manage instead. The create form still offers every field the user may set: one only in items or fields meant for reading lands in its last section. A gallery's photo details leave out what the photo's own view shows only while editing. Lists, cards and filters show a field either way.
797
+ The read view shows the gallery; the form shows the cover photo's field and the table of photos to manage instead. The create form still offers every field the user may set but those [`.notOnCreate()`](#names-and-columns) leaves off: one only in items or fields meant for reading lands in its last section. A gallery's photo details leave out what the photo's own view shows only while editing. Lists, cards and filters show a field either way.
797
798
 
798
799
  ### Editing a record
799
800
 
@@ -9,13 +9,11 @@ An app's resources usually come from its code: a resource in `config/` over a ta
9
9
  ## Tables defined in the database
10
10
 
11
11
  :::caution[Beta]
12
- Tables are added through the API; the admin app has no screens for them yet.
12
+ Tables are added through the API or the admin app.
13
13
  :::
14
14
 
15
15
  An app can keep tables its users add in its own database, beside the resources in its code. Each one is served like any other resource, at its own address with its list, record page, search and API. Every server process serves a new table within a second of it being added.
16
16
 
17
- <gap>Users cannot add a table from the admin app yet because its screens for tables and fields are not implemented.</gap>
18
-
19
17
  ### The switch
20
18
 
21
19
  User tables are off by default. Turn them on in `protobase.config.ts`:
@@ -83,7 +81,7 @@ Measured with six fields per table on one machine running Postgres and the proce
83
81
  ## Adding tables and fields
84
82
 
85
83
  :::caution[Beta]
86
- Tables and fields are added through the API alone, and are never renamed, retyped or removed.
84
+ Tables and fields are added through the API or the admin app, and are never renamed, retyped or removed.
87
85
  :::
88
86
 
89
87
  Someone with the right capability creates a table with its fields, or adds a field to it, through the app's API. It is created in the database at once, with an index for every field but JSON so its list can be filtered by it, and sorted by it unless it is long text, and only admins can see it until access is widened.
@@ -119,6 +117,24 @@ The answer has the table's record, so a client links to the new table at its `ta
119
117
 
120
118
  Building an index holds the table's write lock, and every statement runs within the request's statement timeout (1 s by default). <gap>Adding a field to a table with very many records cannot finish when building its index takes longer than the statement timeout, because building it outside the request is not implemented; the field is then not added.</gap>
121
119
 
120
+ ## In the admin app
121
+
122
+ :::caution[Beta]
123
+ A table is made first and given its fields one by one, and a file cannot be imported into a new table yet.
124
+ :::
125
+
126
+ People who may add tables find **Tables** in the admin app, make a table with its fields and add fields later, on a computer or a phone. The new table appears in the sidebar at once.
127
+
128
+ The screens are the admin app's standard list, record page and create forms over the [two resources](#adding-tables-and-fields), so they work the same in a browser, the installed web app and the native app, and come in [English and Dutch](languages.md).
129
+
130
+ - **Tables.** The sidebar's **Tables** group leads with a **Tables** entry, shown only to those who may create tables (`user_tables.create`, or admins without roles), and holds every table users added. The list shows each table's name and address.
131
+ - **A new table.** **New table** asks for the plural label, the singular label and the name; the title field is set later, as no field exists yet. Saving opens the table's page.
132
+ - **A table's page.** It shows the table's labels and name, and its fields in their order. **Add field** opens the form for a new field of the table, and **Open table** the table's own list, at its address. On a phone both sit in the top bar's **Record actions** menu.
133
+ - **A new field.** The form asks for the label, the name, the type, chosen by its name in words, such as **Long text** or **Yes or no**, and the position, and for a decimal number its digits and decimals. Every field users add is optional, so the form does not ask.
134
+ - **What changes later.** On a table's page, its labels and its title field, the name of one of its text fields; on a field's page, its label and position. The name, the type and a decimal's digits and decimals stay as made.
135
+
136
+ <gap>A table cannot be made together with its fields in one form because a create form that adds related records is not implemented; each field is added from the table's page.</gap>
137
+
122
138
  ## Taking a table over as code
123
139
 
124
140
  :::caution[Beta]