pgkiln 0.38.0 → 0.40.0

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.
Files changed (56) hide show
  1. package/SECURITY.md +3 -0
  2. package/db/migrations/093_workspace_preferences.sql +34 -0
  3. package/db/migrations/094_task_vacation_expiry.sql +182 -0
  4. package/docs/apex-feature-parity.md +8 -8
  5. package/docs/guide/03-builder.md +62 -2
  6. package/docs/guide/04-pages-and-regions.md +27 -5
  7. package/docs/guide/06-processing.md +27 -1
  8. package/docs/guide/12-development.md +15 -6
  9. package/docs/guide/13-rest-api.md +3 -1
  10. package/examples/hr/hr_56_more_charts.sql +57 -0
  11. package/examples/hr/hr_57_task_policies.sql +18 -0
  12. package/examples/hr/hr_58_facet_charts.sql +17 -0
  13. package/package.json +1 -1
  14. package/public/app.css +30 -0
  15. package/public/sw.js +2 -1
  16. package/src/app.ts +2 -0
  17. package/src/automations.ts +6 -0
  18. package/src/blueprint-from-app.ts +240 -0
  19. package/src/builder/blueprints.ts +31 -1
  20. package/src/builder/components.ts +7 -0
  21. package/src/builder/home.ts +3 -2
  22. package/src/builder/region-settings.ts +13 -5
  23. package/src/builder/sql-utilities.ts +278 -0
  24. package/src/builder/sql.ts +3 -1
  25. package/src/builder/ui.ts +16 -3
  26. package/src/builder/workspace-backups.ts +78 -0
  27. package/src/builder/workspaces.ts +83 -1
  28. package/src/cli/replace.ts +2 -0
  29. package/src/i18n/ar.ts +25 -0
  30. package/src/i18n/cs.ts +25 -0
  31. package/src/i18n/da.ts +25 -0
  32. package/src/i18n/de.ts +25 -0
  33. package/src/i18n/el.ts +25 -0
  34. package/src/i18n/es.ts +25 -0
  35. package/src/i18n/fi.ts +25 -0
  36. package/src/i18n/fr.ts +25 -0
  37. package/src/i18n/he.ts +25 -0
  38. package/src/i18n/it.ts +25 -0
  39. package/src/i18n/ja.ts +25 -0
  40. package/src/i18n/ko.ts +25 -0
  41. package/src/i18n/nb.ts +25 -0
  42. package/src/i18n/pl.ts +25 -0
  43. package/src/i18n/pt.ts +25 -0
  44. package/src/i18n/ru.ts +25 -0
  45. package/src/i18n/sv.ts +25 -0
  46. package/src/i18n/tr.ts +25 -0
  47. package/src/i18n/uk.ts +25 -0
  48. package/src/i18n/zh.ts +25 -0
  49. package/src/i18n.ts +50 -0
  50. package/src/runtime/charts.ts +154 -5
  51. package/src/runtime/facet-state.ts +3 -0
  52. package/src/runtime/facets.ts +25 -0
  53. package/src/runtime/rest.ts +8 -2
  54. package/src/runtime/tasks.ts +62 -4
  55. package/src/session.ts +5 -0
  56. package/src/workflow.ts +6 -4
package/SECURITY.md CHANGED
@@ -70,6 +70,9 @@ first version (MVP). Every finding below has a regression test in
70
70
  | Workspaces (0.29.0) | Every builder request under `/apps/:id` or `/pages/:pid` (GET and POST), a working copy's main application, a boilerplate and the code editor's application are checked against the developer's workspaces in `developer()` and answer 404 outside them; lists are filtered by the current workspace. Switching needs CSRF and membership; managing workspaces needs an administrator and CSRF; names are escaped; subscriptions only reach theme and library applications of the same workspace. Workspace tables are closed to the runtime role. **A shared workspace is not a tenant boundary:** its SQL Workshop runs as the owner and its application code on the shared runtime connection; isolate a workspace for that (below). |
71
71
  | Isolated workspaces (sprint 60) | The boundary is the PostgreSQL login (`session_user`, which SQL can't change): an isolated workspace's applications run on its own run login and its developers' SQL on its own developer login (`src/workspace-db.ts`; passwords derived from `PGKILN_SECRET_KEY`, never stored), so `RESET ROLE` or `set_config('role', …)` only reach that login. The run login has `pgkiln_app_code` (the runtime role's rights in `meta`, kept in step after every migration by `meta.sync_app_code_grants()`, checked by a test) and the workspace's roles; the developer login only the workspace's roles. Row-level security on every `meta` table the runtime reads keys on the login (`meta.visible_apps()`), both ways: isolated workspaces don't see others, the shared runtime doesn't see isolated ones; the owner isn't affected. `meta.app_id()` and the eight functions that take an application id (`meta.checked_app`) refuse an application the login may not see. A workspace's database roles are an administrator's list (`meta.workspace_role`): an application there may only run as one of them, a role belongs to one workspace, and superusers, roles with CREATEROLE/BYPASSRLS/REPLICATION and pgkiln's own roles are refused; memberships follow by triggers (`meta.workspace_sync_roles()`). A new application gets a new role and a new or own schema. Every builder tool that runs a developer's SQL uses the workspace's developer login (`workshopDb`, `devDbForApp`); the import check plans the imported SQL on it, outside the import's owner transaction. Saved scripts, runs, blueprints and generators are per workspace. **Not covered:** administrators and developers who also belong to a shared workspace (owner rights there), the `pg_catalog` (definitions are readable by every login), PostgREST (one authenticator), identity providers, LDAP, mail and AI services (installation-wide). |
72
72
  | Builder screens like APEX's (sprint 61) | The sign-in page's workspace is checked after the password (only the developer's workspaces; a wrong one answers like a wrong password, failures are throttled and logged as before); *Remember workspace and username* is an HttpOnly cookie for `/builder` holding no secret, read defensively and escaped; the redirect after sign-in is a `/builder` path only. Workspace message and banner: administrators and the workspace's administrators, CSRF, plain text escaped, the banner colour from a fixed list. Utilization: the same. SQL Workshop home: your own recent commands only (by id and username), the schema checked against the schemas the session's connection may use (and set as a parameter of `set_config`). Application groups: every query bound to the current workspace; groups and applications of other workspaces are ignored. App Gallery: keys are names from `examples/gallery/gallery.json`, never paths; sample apps (shipped SQL as the owner, demo accounts with known passwords) are for administrators only; starter apps go through the blueprint review and its signed create step. Cross application reports: fixed SQL per topic, the workspace id as a parameter. |
73
+ | Workspace preferences, extension links, Manage backups (sprint 62) | Preferences and links: administrators and the workspace's administrators, CSRF, logged. The SQL Workshop preference is checked in `developer()` for every `/builder/sql` GET and POST before a route runs, so no SQL Workshop route can skip it; it is not a sandbox (a developer of several workspaces uses it in another; isolation is the boundary). RESTful services off: the module lookup answers 404 before authentication or any handler SQL. Extension links: only `http://`/`https://` URLs without spaces, quotes or angle brackets (checked in Node and by a CHECK constraint), escaped, opened with `rel="noopener noreferrer"`. Manage backups: only the current workspace's applications, through the existing `meta.backup_app`. SQL Workshop utilities: fixed catalogue SQL chosen by a key from a fixed list (own-property lookup), run read only with a statement timeout on the SQL Workshop's connection (an isolated workspace's developer login sees only its roles' schemas); the Database Monitor shows other sessions' query texts, so it is for administrators only and cancels nothing. |
74
+ | Approval vacation rules and expiration (sprint 64) | A vacation rule is always the signed-in user's own (`meta.app_user()` in a security-definer function; no user field is read from the form); its table is closed to the runtime roles; the delegate's rights are computed in `meta.task_rights` on every call and only for tasks the user owns by name or is assigned (never role ownership), so they end with the rule. Dates are checked as dates; CSRF and an in-app `next`. The expiry tick runs as the owner from the scheduler only (no grant). |
75
+ | Blueprint from an application (sprint 66) | Builder only, CSRF; the application must be in the developer's current workspace (administrators: any). The catalog is read as the owner but only for tables the application's database role may SELECT, never `meta`. Sample rows are opt-in (the page warns about personal data), read in a read-only transaction with a statement timeout through `devDbForApp` (an isolated workspace's developer login), at most 20 per table. The result only fills the editor: creating still goes through the signed review. |
73
76
  | Drawers, built-in template components, Theme Roller additions (0.29.0) | Dialog positions and sizes, base styles, dark-mode colours, item and report-column template options only reach the page from fixed lists (checked when saved and again when rendered: a hand-edited value is ignored); the browser gets dialog shapes as JSON values from those lists. Built-in template components pass the same template checks as plug-ins; *Copy into this application* takes a static id from the fixed list, needs CSRF and the application in the developer's workspace, and never overwrites the application's own component. |
74
77
  | Report row selection across pages (0.29.0) | `POST …/report/<id>/select` needs the CSRF token, page access and a visible report region whose `selection` names the item (and the item visible); it only changes that item's session state; values are bounded (5000, 400 characters, no `:`) and treated as user input, like a submit; output is escaped. |
75
78
  | Instance settings (0.29.0) | Administrators only, with CSRF; only whole numbers within each setting's range are stored (a bad stored value falls back to the environment variable or default); the configuration overview never shows secrets (connection strings, keys: only whether they are set); changes are logged.
@@ -0,0 +1,34 @@
1
+ -- =====================================================================
2
+ -- 093: workspace preferences and extension links (APEX: Administration →
3
+ -- Manage Service → Set Workspace Preferences, Manage Extension Links)
4
+ --
5
+ -- sql_workshop false: the SQL Workshop (SQL Commands, Object Browser,
6
+ -- scripts, data loading, Quick SQL, query builder, DDL and
7
+ -- compare, AI) is refused while the workspace is current
8
+ -- rest_services false: the REST modules of the workspace's applications
9
+ -- answer 404 at run time (/a/<alias>/rest/...)
10
+ --
11
+ -- meta.workspace_link: links to other tools (monitoring, a wiki, a ticket
12
+ -- system) shown in the builder's Extensions menu while the workspace is current.
13
+ --
14
+ -- Administrators and the workspace's administrators set them.
15
+ -- Installation data, not exported.
16
+ -- =====================================================================
17
+ alter table meta.workspace
18
+ add column sql_workshop boolean not null default true,
19
+ add column rest_services boolean not null default true;
20
+ comment on column meta.workspace.sql_workshop is 'Workspace preference (093): false refuses the SQL Workshop while the workspace is current.';
21
+ comment on column meta.workspace.rest_services is 'Workspace preference (093): false turns off the REST modules of the workspace''s applications.';
22
+
23
+ create table meta.workspace_link (
24
+ id serial primary key,
25
+ workspace_id int not null references meta.workspace on delete cascade,
26
+ label text not null check (label ~ '^\S(.{0,58}\S)?$'),
27
+ url text not null check (url ~ '^https?://[^\s<>"]+$' and length(url) <= 2000),
28
+ description text check (length(description) <= 500),
29
+ position int not null default 0,
30
+ unique (workspace_id, label)
31
+ );
32
+ revoke all on meta.workspace_link from public;
33
+ comment on table meta.workspace_link is 'Extension links (093): a workspace''s links to other tools, in the builder''s Extensions menu. Not exported.';
34
+
@@ -0,0 +1,182 @@
1
+ -- =====================================================================
2
+ -- 094: approvals: vacation rules, and expiry, renewal and escalation of
3
+ -- overdue tasks (APEX: Task Definition → Vacation Rule, Expiration Policy)
4
+ --
5
+ -- Vacation rules: a user of an application names a delegate and a period
6
+ -- (meta.set_vacation_rule from the task list). While the rule is active the
7
+ -- delegate may act on the tasks the user may act on as a named owner
8
+ -- (owner_users) or as the actual owner, checked live in meta.task_rights:
9
+ -- when the rule ends, so does the delegate's access. Role owners are not
10
+ -- passed on: the role's other members are there for that.
11
+ --
12
+ -- Expiration policy per task definition (null: overdue tasks stay open):
13
+ -- expire an overdue task ends with state 'expired' (no outcome)
14
+ -- renew its due date moves on by "due in" (up to max_renewals times,
15
+ -- default 3), then it expires
16
+ -- escalate once: escalate_role becomes a potential owner, the priority 1,
17
+ -- the task unassigned with a new due date; overdue again: expired
18
+ -- meta.task_expire_tick() applies it (the server's scheduler, every minute).
19
+ -- A workflow waiting for a task that expires goes on with the outcome
20
+ -- "expired" (or ends, like a cancelled task, without an "expired" branch).
21
+ -- =====================================================================
22
+
23
+ alter table meta.task_definition
24
+ add column expiration text check (expiration in ('expire', 'renew', 'escalate')),
25
+ add column max_renewals int check (max_renewals between 1 and 100),
26
+ add column escalate_role text check (escalate_role ~ '^\S{1,100}$');
27
+ comment on column meta.task_definition.expiration is 'Expiration policy (094): expire, renew or escalate an overdue task; null: it stays open.';
28
+
29
+ alter table meta.task
30
+ add column renewals int not null default 0,
31
+ add column escalated_at timestamptz;
32
+ alter table meta.task drop constraint task_state_check;
33
+ alter table meta.task add constraint task_state_check check (state in ('unassigned', 'assigned', 'completed', 'cancelled', 'expired'));
34
+ alter table meta.task_event drop constraint task_event_event_check;
35
+ alter table meta.task_event add constraint task_event_event_check
36
+ check (event in ('created', 'claimed', 'released', 'delegated', 'approved', 'rejected', 'completed', 'cancelled', 'commented', 'closed',
37
+ 'expired', 'renewed', 'escalated'));
38
+
39
+ -- ---------------------------------------------------------------- vacation rules
40
+ create table meta.task_vacation (
41
+ app_id int not null references meta.app on delete cascade,
42
+ username text not null check (username = lower(username) and length(username) between 1 and 100),
43
+ delegate_to text not null check (delegate_to = lower(delegate_to) and length(delegate_to) between 1 and 100),
44
+ starts_at timestamptz not null,
45
+ ends_at timestamptz not null,
46
+ created_at timestamptz not null default now(),
47
+ primary key (app_id, username),
48
+ check (delegate_to <> username),
49
+ check (ends_at > starts_at)
50
+ );
51
+ create index on meta.task_vacation (app_id, delegate_to);
52
+ revoke all on meta.task_vacation from public;
53
+ comment on table meta.task_vacation is 'Vacation rules (094): while active, delegate_to acts on the user''s tasks. User data, not exported.';
54
+
55
+ /** The users (lower case) of this application whose active vacation rule names the current user. */
56
+ create function meta.acting_for() returns text[]
57
+ language sql stable security definer set search_path = meta, pg_catalog as $$
58
+ select coalesce(array_agg(v.username order by v.username), '{}')
59
+ from task_vacation v
60
+ where v.app_id = meta.app_id() and v.delegate_to = lower(meta.app_user()) and now() >= v.starts_at and now() < v.ends_at
61
+ $$;
62
+
63
+ create function meta.set_vacation_rule(p_delegate text, p_from timestamptz, p_until timestamptz) returns void
64
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
65
+ declare
66
+ v_user text := lower(meta.app_user());
67
+ v_to text := lower(btrim(coalesce(p_delegate, '')));
68
+ v_from timestamptz := coalesce(p_from, now());
69
+ begin
70
+ if v_user is null or v_user = 'nobody' then
71
+ raise exception 'Sign in to set a vacation rule.';
72
+ end if;
73
+ if v_to = '' or length(v_to) > 100 or v_to ~ '\s' then
74
+ raise exception 'Name the user who takes over your tasks.';
75
+ end if;
76
+ if v_to = v_user then
77
+ raise exception 'You cannot delegate your tasks to yourself.';
78
+ end if;
79
+ if p_until is null or p_until <= v_from or p_until <= now() then
80
+ raise exception 'The vacation rule ends after it starts, and in the future.';
81
+ end if;
82
+ if p_until > v_from + interval '1 year' then
83
+ raise exception 'A vacation rule lasts at most a year.';
84
+ end if;
85
+ insert into task_vacation (app_id, username, delegate_to, starts_at, ends_at)
86
+ values (meta.app_id(), v_user, v_to, v_from, p_until)
87
+ on conflict (app_id, username) do update
88
+ set delegate_to = excluded.delegate_to, starts_at = excluded.starts_at, ends_at = excluded.ends_at, created_at = now();
89
+ end
90
+ $$;
91
+
92
+ create function meta.clear_vacation_rule() returns void
93
+ language sql security definer set search_path = meta, pg_catalog as $$
94
+ delete from task_vacation where app_id = meta.app_id() and username = lower(meta.app_user());
95
+ $$;
96
+
97
+ /** The current user's vacation rule in this application (none: no row). */
98
+ create function meta.vacation_rule() returns table (delegate_to text, starts_at timestamptz, ends_at timestamptz, active boolean)
99
+ language sql stable security definer set search_path = meta, pg_catalog as $$
100
+ select v.delegate_to, v.starts_at, v.ends_at, now() >= v.starts_at and now() < v.ends_at
101
+ from task_vacation v
102
+ where v.app_id = meta.app_id() and v.username = lower(meta.app_user()) and v.ends_at > now()
103
+ $$;
104
+
105
+ revoke all on function meta.acting_for(), meta.set_vacation_rule(text, timestamptz, timestamptz), meta.clear_vacation_rule(), meta.vacation_rule() from public;
106
+ grant execute on function meta.acting_for(), meta.set_vacation_rule(text, timestamptz, timestamptz), meta.clear_vacation_rule(), meta.vacation_rule() to public;
107
+
108
+ -- Same as 072, plus: the delegates of users on vacation act for them (named owners and actual owners).
109
+ create or replace function meta.task_rights(t meta.task) returns meta.task_rights
110
+ language plpgsql stable security definer set search_path = meta, pg_catalog as $$
111
+ declare
112
+ v_user text := lower(meta.app_user());
113
+ v_for text[] := meta.acting_for();
114
+ d meta.task_definition;
115
+ r meta.task_rights;
116
+ v_open boolean := t.state in ('unassigned', 'assigned');
117
+ v_mine boolean := lower(t.actual_owner) = v_user or lower(t.actual_owner) = any (v_for);
118
+ begin
119
+ select * into d from task_definition where id = t.definition_id;
120
+ r.is_initiator := lower(t.initiator) = v_user;
121
+ r.is_owner := exists (select 1 from unnest(t.owner_users) u where lower(u) = v_user or lower(u) = any (v_for))
122
+ or exists (select 1 from unnest(t.owner_roles) x where meta.has_role(x));
123
+ r.is_admin := d.admin_role is not null and meta.has_role(d.admin_role);
124
+ r.may_see := t.app_id = meta.app_id() and v_user <> 'nobody'
125
+ and t.tenant_id is not distinct from meta.tenant_id()
126
+ and (r.is_initiator or r.is_owner or r.is_admin or coalesce(v_mine, false));
127
+ -- an initiator may not decide on their own request (unless the definition allows it)
128
+ r.may_act := r.may_see and v_open and (d.initiator_can_complete or not r.is_initiator)
129
+ and (coalesce(v_mine, false) or (t.state = 'unassigned' and r.is_owner));
130
+ r.may_claim := r.may_see and t.state = 'unassigned' and r.is_owner and (d.initiator_can_complete or not r.is_initiator);
131
+ r.may_release := r.may_see and t.state = 'assigned' and coalesce(v_mine, false);
132
+ r.may_delegate := r.may_see and v_open and (coalesce(v_mine, false) or r.is_admin);
133
+ r.may_cancel := r.may_see and v_open and (r.is_initiator or r.is_admin);
134
+ return r;
135
+ end
136
+ $$;
137
+
138
+ -- ---------------------------------------------------------------- expiration
139
+ create function meta.task_expire_tick() returns int
140
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
141
+ declare
142
+ x record;
143
+ n int := 0;
144
+ v_due interval;
145
+ begin
146
+ for x in
147
+ select t.id, t.due_at, t.renewals, t.escalated_at, t.owner_roles, d.expiration, d.max_renewals, d.escalate_role, d.due_in
148
+ from task t join task_definition d on d.id = t.definition_id
149
+ where t.state in ('unassigned', 'assigned') and t.due_at < now() and d.expiration is not null
150
+ order by t.due_at
151
+ limit 500
152
+ for update of t skip locked
153
+ loop
154
+ v_due := case when x.due_in is not null then x.due_in::interval end;
155
+ if x.expiration = 'renew' and v_due is not null and x.renewals < coalesce(x.max_renewals, 3) then
156
+ update task set due_at = greatest(x.due_at + v_due, now() + v_due), renewals = renewals + 1 where id = x.id;
157
+ insert into task_event (task_id, username, event, detail)
158
+ values (x.id, 'system', 'renewed', format('%s of %s', x.renewals + 1, coalesce(x.max_renewals, 3)));
159
+ elsif x.expiration = 'escalate' and x.escalated_at is null then
160
+ update task
161
+ set owner_roles = case when x.escalate_role is null or x.escalate_role = any (owner_roles) then owner_roles
162
+ else array_append(owner_roles, lower(x.escalate_role)) end,
163
+ priority = 1, state = 'unassigned', actual_owner = null, escalated_at = now(),
164
+ due_at = case when v_due is not null then now() + v_due end
165
+ where id = x.id;
166
+ insert into task_event (task_id, username, event, detail) values (x.id, 'system', 'escalated', x.escalate_role);
167
+ else
168
+ update task set state = 'expired', completed_at = now() where id = x.id;
169
+ insert into task_event (task_id, username, event) values (x.id, 'system', 'expired');
170
+ end if;
171
+ n := n + 1;
172
+ end loop;
173
+ return n;
174
+ end
175
+ $$;
176
+ revoke all on function meta.task_expire_tick() from public;
177
+
178
+ -- Same as 022, plus: an expired task wakes its workflow too.
179
+ drop trigger task_wakes_workflow on meta.task;
180
+ create trigger task_wakes_workflow after update of state on meta.task
181
+ for each row when (new.workflow_id is not null and new.state in ('completed', 'cancelled', 'expired') and old.state is distinct from new.state)
182
+ execute function meta.task_wakes_workflow();
@@ -7,7 +7,7 @@ open item and open an issue or pull request; see [CONTRIBUTING.md](../CONTRIBUTI
7
7
  Legend: ✅ available · 🟡 partial (see notes) · ❌ not yet · ➖ not planned (a deliberate choice,
8
8
  or better served by the PostgreSQL ecosystem; see the notes and [extensions](guide/15-extensions.md))
9
9
 
10
- Last reviewed: 2026-10-13 (sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
10
+ Last reviewed: 2026-10-14 (sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
11
11
 
12
12
  ## At a glance
13
13
 
@@ -64,10 +64,10 @@ application reports, Manage Service), so the share available dropped without any
64
64
  | Interactive grid | ✅ | Inline edit, add and delete rows, lists of values, required columns, per-row errors, all-or-nothing save, signed row keys, search and paging; **aggregates** over the whole result (developer and user defined), **frozen columns**, column **reorder/resize/hide** (Actions → Columns, or drag and drop), per-user layouts and **saved grid reports** (private and public), **master-detail** (signed row selection, details refreshed in place), a **row actions menu** (edit, duplicate, delete, links) and **copy/paste** of cell ranges (tab-separated, spreadsheet compatible) ([chapter 4](guide/04-pages-and-regions.md)) |
65
65
  | Interactive grid views: single row view, chart view, group by | ✅ | (sprint 54) **Single row view**: one row of the search as a form with first/previous/next/last and *Add row*, from the Actions or row actions menu, saved by the grid's own process with the same checks; **group by** and **chart** set up in the Actions menu over every row of the search, as for the interactive report; `views` limits them; Save comes back to the same search, page and view ([chapter 4](guide/04-pages-and-regions.md#grid-interactive-grid)). No pivot view on grids (the interactive report has one), no editing in the single row view of columns the grid doesn't show |
66
66
  | Form (automatic row processing) | ✅ | Fetch, insert, update, delete, in a page or a modal dialog. Detects rows deleted meanwhile. (sprint 50) **Lost update detection** (APEX: *Prevent Lost Updates* and *Lock Row*): the page carries a checksum of the row's form columns (`meta.row_version`, independent of time zone and date format, as the app's role); *Save* and *Delete* lock the row and refuse when it changed meanwhile; off per form with `{"lost_update": false}` ([chapter 4](guide/04-pages-and-regions.md#form)) |
67
- | Charts | ✅ | Bar, column, stacked, line, area, combination (columns + lines), scatter, bubble, donut, pie, gauge, funnel, radar, **Gantt** (start/end, progress, milestones, dependencies, a today line, a time axis from hours to years), **pyramid** (area-proportional segments, or two series back to back) and **polar** (polar area); multi-series, tooltips, data table, palette checked for colour-vision deficiency; drill-down links on chart marks (checksummed, only to pages the user may open). Drawn on the server as SVG without inline styles. **Missing:** other Oracle JET types (e.g. stock, box plot, range), editing a Gantt by drag and drop |
67
+ | Charts | ✅ | Bar, column, stacked, line, area, combination (columns + lines), scatter, bubble, donut, pie, gauge, funnel, radar, **Gantt** (start/end, progress, milestones, dependencies, a today line, a time axis from hours to years), **pyramid** (area-proportional segments, or two series back to back) and **polar** (polar area); (sprint 63) **range** (floating columns from low to high), **box plot** (from one value per row or five statistics) and **stock** (candlesticks from open, high, low and close); multi-series, tooltips, data table, palette checked for colour-vision deficiency; drill-down links on chart marks (checksummed, only to pages the user may open). Drawn on the server as SVG without inline styles. **Missing:** other Oracle JET types (e.g. sunburst, treemap, timeline, picto), editing a Gantt by drag and drop |
68
68
  | Cards and metric cards (26.1 metric card template) | ✅ | Cards from SQL, with a KPI "metric" style |
69
69
  | Calendar | ✅ | Month, week, day and list views (agenda list on phones); create on click (a checksummed link with the slot); drag and drop to move events through the developer's move SQL, run as the app's role (CSRF, a *who may drag* authorization, only events the user sees). Works without JavaScript; dragging adds on top ([chapter 4](guide/04-pages-and-regions.md#calendar)) |
70
- | Faceted search | ✅ | Checkbox, range (predefined, or a custom from/to on numbers and dates), star rating and search facets with live counts that honour the other facets; exclude on checkbox facets (26.1). Values travel as query parameters, never as SQL text. **Missing:** facet charts |
70
+ | Faceted search | ✅ | Checkbox, range (predefined, or a custom from/to on numbers and dates), star rating and search facets with live counts that honour the other facets; exclude on checkbox facets (26.1). Values travel as query parameters, never as SQL text. (sprint 65) **Facet charts**: a bar or pie chart of a facet's counts, each mark filtering on its value |
71
71
  | Smart filters | ✅ | One search field with filter chips and suggestions over a report, the same facet types; URL-based, works without JavaScript ([chapter 4](guide/04-pages-and-regions.md#smart_filters)) |
72
72
  | Static content, dynamic content | ✅ | `static` (HTML with substitutions) and `dynamic` (a SELECT returning HTML) |
73
73
  | Breadcrumb | ✅ | Automatic, from the page's breadcrumb parent |
@@ -183,7 +183,7 @@ application reports, Manage Service), so the share available dropped without any
183
183
 
184
184
  | APEX | pgkiln | Notes |
185
185
  |---|---|---|
186
- | SQL Workshop: SQL commands, object browser | ✅ | (sprint 61) A **home page** like APEX's: tiles, recently created tables, your recent SQL commands and the workspace's recent scripts, and the session's **default schema** (first in the search path of SQL Commands and Scripts). The object browser shows columns, RLS policies, grants, data and function source |
186
+ | SQL Workshop: SQL commands, object browser | ✅ | (sprint 61) A **home page** like APEX's: tiles, recently created tables, your recent SQL commands and the workspace's recent scripts, and the session's **default schema** (first in the search path of SQL Commands and Scripts). The object browser shows columns, RLS policies, grants, data and function source. (sprint 62) **Utilities** like APEX's: the tools on one page, **Object Reports** (tables without a primary key, unindexed foreign keys, row-level security without policies or not enabled, PUBLIC grants, unused indexes, largest tables, missing comments), **About Database** and a **Database Monitor** of sessions and blocked queries for administrators. **Missing:** a recycle bin (PostgreSQL has none), user interface defaults (pgkiln's wizards read the catalog and comments) |
187
187
  | RESTful services (ORDS) | ✅ | [PostgREST](https://postgrest.org) next to pgkiln: `api` schema, the same RLS as the UI, per-app API role, tokens in the builder, a pre-request check, and **OAuth clients** (client credentials, like ORDS `oauth.create_client`) so tokens renew themselves |
188
188
  | REST handler editor, REST-enabled SQL | ✅ | **REST modules** in the builder: handlers (method, path with parameters, SQL as collection, item or statements, roles, public) served by pgkiln with bearer tokens and an OpenAPI description; plus PostgREST for schema-wide APIs. **Missing:** REST-enabled SQL (rarely desirable) |
189
189
  | SQL scripts, query builder, Quick SQL | ✅ | **SQL Scripts**: saved, upload/download, a result per statement, stop or continue on errors, optionally one transaction, run history. **Quick SQL** (a subset: tables, child tables, types, the main column directives, `/auditcols`, settings, views). A graphical **query builder**: tables as boxes on a canvas (dragged, placed where left), joins along foreign keys or drawn from column to column (inner or left), column functions with automatic `group by`, conditions, sort, open in SQL Commands; without JavaScript the same as forms ([chapter 3](guide/03-builder.md#sql-workshop)) |
@@ -202,24 +202,24 @@ application reports, Manage Service), so the share available dropped without any
202
202
 
203
203
  | APEX | pgkiln | Notes |
204
204
  |---|---|---|
205
- | Approvals and task list | ✅ | Task definitions (approval / action, owner roles and users, business administrators, priority, due date, details page), `meta.create_task` from application SQL, a task list region (claim, approve/reject/complete with comment, release, delegate, cancel, history), completion SQL in the same transaction. **Missing:** built-in e-mail notifications (send them with `meta.send_mail` from the completion SQL or an automation), vacation rules, expiry/escalation policies |
205
+ | Approvals and task list | ✅ | Task definitions (approval / action, owner roles and users, business administrators, priority, due date, details page), `meta.create_task` from application SQL, a task list region (claim, approve/reject/complete with comment, release, delegate, cancel, history), completion SQL in the same transaction. (sprint 64) **Vacation rules** (a stand-in acts on a user's tasks while the rule is active, checked on every request) and **expiration policies** (expire, renew up to N times, escalate to a role once; a waiting workflow goes on with the outcome `expired`). **Missing:** built-in e-mail notifications (send them with `meta.send_mail` from the completion SQL or an automation) |
206
206
  | Workflow (26.1: parallel flows, multi-tenancy) | ✅ | Workflow definitions of task, SQL, switch, wait, **invoke API** (a REST data source or URL through the invoke-API code, response values and the HTTP status into variables, retryable faults, no transaction open during the call) and end steps with variables, started from application SQL, run by the server (NOTIFY + polling) as the app's role; console region (terminate, retry a faulted step); diagram in the builder. **Parallel branches** (split, then a join that waits for all or for the first branch) and **versions** (development, active, inactive; running instances keep theirs) ([chapter 6](guide/06-processing.md#workflows)). (0.31) **Tenants**: `meta.set_tenant()` (APEX: `APEX_SESSION.SET_TENANT_ID`); workflows, their tasks, background chains, the task list, the console and every action follow the session's tenant ([chapter 6](guide/06-processing.md#tenants)). No *Send E-Mail* activity: send mail with `meta.send_mail` from an SQL step |
207
207
  | Automations (scheduled) | ✅ | Shared Components → Automations: cron schedules with time zones, once or per row of a query, **several ordered actions** with their own conditions, **error handling** (stop, skip the row and continue, or disable; row errors in the run history), roles, run history and Run now, **on-demand runs from SQL** with `meta.run_automation()` (like `APEX_AUTOMATION.EXECUTE`, synchronous in the caller's transaction), safe with several servers ([chapter 6](guide/06-processing.md#automations)) |
208
208
  | AI assistant, natural-language reports (NL2IR), AI agents and tools (26.1) | ✅ | Region type **AI assistant**: a conversation per session, context queries (RAG over developer queries, run as the app's role, read-only), and **tools** the model may call: SQL with bound arguments checked against declared parameters (read-only unless marked as writing, row limit, timeout) and REST data sources, each optionally behind an authorization scheme. **Natural-language report filters**: an "Ask in your own words" box turns a question into the report's normal filters, search and sort, limited to its visible columns ([chapter 4](guide/04-pages-and-regions.md)). Agents are defined per region (not as shared components); answers are not streamed. `pgvector` covers semantic search on the data side |
209
209
  | *Generate Text with AI* process, structured outputs (26.1) | ✅ | **AI services** (Workspace utilities, administrators): Claude through `@anthropic-ai/sdk` or OpenAI through `openai`, encrypted write-only keys (or `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`), per-app access with daily request and token limits, a usage log. Page process `ai_generate` (text into an item, or structured output into several items with a schema built from the items or your own) and a dynamic action that runs it without a page submit; `meta.ai_generate` / `ai_result` / `ai_available` from SQL ([chapter 6](guide/06-processing.md)). Prompts, item values included, go to the chosen provider |
210
- | Blueprints, spec-driven development (26.1) | ✅ | Create → **From a blueprint**: a JSON spec of tables (types, required, unique, allowed values, references), pages, navigation and sample data; optionally drafted by AI from a description; always reviewed (problems, SQL, pages, menu) before the application is created in one transaction; saved blueprints. **Missing:** a blueprint from an existing application |
210
+ | Blueprints, spec-driven development (26.1) | ✅ | Create → **From a blueprint**: a JSON spec of tables (types, required, unique, allowed values, references), pages, navigation and sample data; optionally drafted by AI from a description; always reviewed (problems, SQL, pages, menu) before the application is created in one transaction; saved blueprints; (sprint 66) **a blueprint from an existing application** (its tables, pages, menu and optionally sample rows, with notes on what didn't carry over) |
211
211
 
212
212
  ## Administration
213
213
 
214
214
  | APEX | pgkiln | Notes |
215
215
  |---|---|---|
216
216
  | Developer accounts, workspace sign-in | ✅ | (sprint 61) The builder's sign-in page like APEX's: workspace (optional; it becomes the current one, a wrong one answers like a wrong password), username, password with a Show button, *Remember workspace and username*, a link to Administration |
217
- | Workspace message, environment banner, workspace utilization (Manage Service) | ✅ | (sprint 61) An **Administration** menu in the icon rail; administrators and workspace administrators set a **workspace message** (on the App Builder home) and an **environment banner** (a coloured label on every builder page of the workspace), and see **workspace utilization** (applications, pages, page views, users, static files, schema storage) ([chapter 3](guide/03-builder.md#workspace-message-and-environment-banner)). **Missing:** extension links, workspace-level preferences such as disabling the SQL Workshop |
217
+ | Workspace message, environment banner, workspace utilization, preferences and extension links (Manage Service) | ✅ | (sprint 61) An **Administration** menu in the icon rail; administrators and workspace administrators set a **workspace message** (on the App Builder home) and an **environment banner** (a coloured label on every builder page of the workspace), and see **workspace utilization** (applications, pages, page views, users, static files, schema storage) ([chapter 3](guide/03-builder.md#workspace-message-and-environment-banner)). (sprint 62) **Workspace preferences**: the SQL Workshop and the applications' RESTful services (REST modules) on or off per workspace; **extension links** in an Extensions menu of the rail ([chapter 3](guide/03-builder.md#workspace-preferences)). **Missing:** the account login controls of APEX's preferences (pgkiln's password policy and sign-in throttling are installation-wide) |
218
218
  | Instance administration, install/upgrade logs (26.1) | ✅ | Versioned migrations (`npm run db:migrate`), every run logged; Workspace utilities → **Installation** (administrators): server version, install and upgrade runs with errors, applied migrations, and warnings for migrations the database misses or the server doesn't know ([chapter 3](guide/03-builder.md)). Workspace utilities → **Instance settings**: session length and sign-in throttling (stored in the database, over the environment variables) and an overview of the server's configuration without secrets; a server whose database lacks migrations says so (503) or applies them (`MIGRATE_ON_START`). Other settings stay environment variables |
219
219
  | Debug messages | ✅ | Debug levels 1–9 per app; `meta.debug(level, text)` and `meta.debug_enabled(level)` from application SQL (APEX_DEBUG); timed entries per request (page steps, regions, processes, branches, errors, SQL notices); Activity → **Debug messages** viewer per page view with filters and the slowest step highlighted; retention 1–90 days; password item values are never recorded ([chapter 6](guide/06-processing.md)). (sprint 50) Debug for **one session** from the developer toolbar, the application staying out of debug. **Missing:** debug entries for background jobs and REST requests |
220
220
  | Developer toolbar, session state viewer | ✅ | (sprint 50) Developers signed in to the builder see a toolbar on the pages of their applications in the same browser: App Builder, the application, **Edit page** (Page Designer), **Session** (the session state viewer: user, roles, session, language, time zone, debug level, this page's items, application items and other pages' items; passwords never shown; clear the page's items) **Quick edit** (sprint 59: click a region, item or button to open it in the Page Designer) and **Debug** for this session only. A separate random token (cookie path `/a`, HttpOnly) of a live builder session of a developer of the application's workspace ([chapter 3](guide/03-builder.md#developer-toolbar)). **Missing:** Theme Roller from the page |
221
221
  | Feedback (users send feedback from a page, developers answer) | ✅ | (sprint 51) With *Users can send feedback* on, the navigation bar's **Feedback** link opens a page for a comment, idea or problem about the page (optional rating), with the user's earlier feedback and the developers' answers; developers read, filter, set a status and answer under Application → Feedback ([chapter 3](guide/03-builder.md#feedback)). No screenshots, no e-mail to developers |
222
- | Automatic application backups, page groups | ✅ | (sprint 51) **Backups**: before every import that replaces an application (CLI, MCP, working copy merge, restore), daily when it changed, and by hand; the newest 25 (configurable) are kept; download and restore from the builder ([chapter 3](guide/03-builder.md#backups)). (sprint 58) **Page groups**: Shared Components → Page groups, a page's *Page group*, the page list by group with a filter ([chapter 3](guide/03-builder.md#page-groups)) |
222
+ | Automatic application backups, page groups | ✅ | (sprint 51) **Backups**: before every import that replaces an application (CLI, MCP, working copy merge, restore), daily when it changed, and by hand; the newest 25 (configurable) are kept; download and restore from the builder ([chapter 3](guide/03-builder.md#backups)); (sprint 62) Workspace utilities → **Manage backups** across the workspace's applications, with a backup of all of them at once. (sprint 58) **Page groups**: Shared Components → Page groups, a page's *Page group*, the page list by group with a filter ([chapter 3](guide/03-builder.md#page-groups)) |
223
223
  | Application availability (Available, Developers only, Restricted access, Unavailable; build status *Run only*) | ✅ | (sprint 51) Available, available to developers only (a builder session in the same browser), restricted to named users, or unavailable with a message, for pages, file downloads, REST modules and PostgREST; build status **Run only** refuses builder changes as if the application were locked, while `pgkiln import --replace` still deploys. Installation settings: not exported, kept by an import ([chapter 3](guide/03-builder.md#availability-and-build-status)) |
224
224
  | Monitoring (top SQL) | ✅ | Activity per app; Top SQL per app's database role from `pg_stat_statements` (sortable, resettable) |
225
225
  | Workspaces (multi-tenant) | ✅ | Workspaces group applications and developers: the builder shows and opens only the applications of a developer's workspaces (administrators: all), a current workspace with a switcher, new and imported applications go into it, administrators add workspaces, choose their developers and move applications ([chapter 3](guide/03-builder.md#workspaces)). (sprint 60) **Isolated workspaces** are a tenant boundary: the applications run on a PostgreSQL login of the workspace's own and its developers' SQL Workshop on another, with row-level security on pgkiln's tables, so SQL that resets its role can't reach other workspaces' applications, data, sessions or accounts; **workspace database roles** (APEX: workspace schemas) assigned by administrators; **workspace administrators** who manage their workspace's developers; SQL scripts, blueprints and sample data per workspace ([isolated workspaces](guide/03-builder.md#isolated-workspaces)). **Missing:** user directories per workspace (accounts are installation-wide, visible to the workspaces whose applications they use), developers created by a workspace administrator |
@@ -69,6 +69,29 @@ Banner): a **message** (plain text, up to 2000 characters) shown to the workspac
69
69
  and an **environment banner**: a label of up to 40 characters in one of eight colours, shown at the top of every
70
70
  builder page while the workspace is current.
71
71
 
72
+ ### Workspace preferences
73
+
74
+ Administrators and the workspace's administrators set them on the workspace's page (Administration menu →
75
+ *Workspace preferences*; APEX: Set Workspace Preferences):
76
+
77
+ - **Enable the SQL Workshop**: off, every SQL Workshop page (SQL Commands, the Object Browser, SQL scripts, Load and
78
+ Unload Data, Quick SQL, the query builder, Sample Data, DDL, Compare, AI) answers *403* while the workspace is
79
+ current, for every developer including administrators; the rail and Workspace utilities no longer show it. A
80
+ developer who belongs to another workspace can still use it there. Turn it off for a workspace whose developers
81
+ should build applications only, for example a shared workspace, where the SQL Workshop runs as pgkiln's owner.
82
+ - **Enable RESTful services**: off, the [REST modules](13-rest-api.md#rest-modules-in-the-builder) of the workspace's applications answer
83
+ *404* at `/a/<alias>/rest/…` (also their OpenAPI description); they can still be edited, and they come back
84
+ when it is turned on again. PostgREST is not affected.
85
+
86
+ Both are on by default (migration 093) and are not exported.
87
+
88
+ ### Extension links
89
+
90
+ Links to the other tools a workspace's developers use (monitoring, a wiki, a ticket system; APEX: Manage Extension
91
+ Links), added on the workspace's page by administrators and the workspace's administrators: a label, an `http://` or
92
+ `https://` URL, an optional description and an order. While the workspace is current an **Extensions** icon in the
93
+ rail opens them, each in a new tab (`rel="noopener noreferrer"`).
94
+
72
95
  ### Workspace utilization
73
96
 
74
97
  Administration menu → **Workspace utilization** (`/builder/workspaces/<id>/utilization`; APEX: Workspace
@@ -330,6 +353,24 @@ in **one transaction**: if anything fails, nothing is created. Creation only acc
330
353
  text the review showed you (it is signed with your session); change it and review again. **Save**
331
354
  keeps a blueprint in the list (`meta.blueprint`, with the application last created from it).
332
355
 
356
+ **From an application** (on the Blueprints page; APEX 26.1: a blueprint from an application) opens a blueprint of an
357
+ application of the current workspace in the editor, for a copy, a new version or a test system:
358
+
359
+ - **tables**: the tables its regions read (a form's table, the first `schema.table` after `FROM` in a query) that the
360
+ application's database role may read, with their columns: types (integers, numbers, dates, timestamps, booleans,
361
+ text; other types become text, binary columns are left out), *required* (NOT NULL without a default), *unique*
362
+ (a one-column unique constraint), allowed values (an enum, or a `CHECK (col IN (…))`) and references to another of
363
+ these tables' `id`. Every blueprint table gets an `id`: an old key with another name stays a required, unique column,
364
+ and a reference to it a plain column;
365
+ - **pages**: a report with its form page becomes `report_form`, and grids, cards, calendars, charts, maps, faceted
366
+ searches and forms keep their type and page number; other pages become `blank` pages with their name;
367
+ - **navigation**: the menu entries of those pages;
368
+ - **sample rows** only when ticked: up to 20 per table, read read-only through the application's developer
369
+ connection. They are copied into the blueprint as they are, so leave this off for personal or confidential data.
370
+
371
+ The new name, alias and schema end in *copy*; a list of **notes** says what didn't carry over. Nothing is created
372
+ until you review and confirm it, as for any blueprint.
373
+
333
374
  ### Importing
334
375
 
335
376
  **Import** (a tile, or `/builder/import`): paste the JSON of an export and optionally give a new
@@ -529,10 +570,10 @@ out, other keys are kept, columns the query no longer returns stay listed so you
529
570
  | Region | Settings form |
530
571
  |---|---|
531
572
  | `grid` | Rows per page; whether users may add, change and delete rows; per column its heading, shown, read-only, required and "Edit as" (a shared list of values); aggregates in the footer, frozen columns, the Actions menu, saved and public grid reports, the row actions menu (edit link, duplicate, delete) and master-detail (the master's column and item, the detail's item and column) |
532
- | `chart` | Chart type (also Gantt, pyramid and polar, with the columns each expects), the text when there are no rows, the gauge's range and thresholds, and the drill-down link (page and items, `#column#` and `#series#`); lists the query's columns |
573
+ | `chart` | Chart type (also Gantt, pyramid, polar, range, box plot and stock, with the columns each expects), the text when there are no rows, the gauge's range and thresholds, and the drill-down link (page and items, `#column#` and `#series#`); lists the query's columns |
533
574
  | `cards` | Cards or KPI tiles, the text when there are no rows, and the link (page and items) |
534
575
  | `calendar` | The views and the one shown first, the hours of the week and day views, the edit link of each event, the create-on-click link (`#start#`, `#end#`, `#date#`), and drag and drop (the SQL with `:EVENT_ID`, `:NEW_START`, `:NEW_END`, the key column, who may drag); warns when the query lacks `start_date` or `title` |
535
- | `facets` | The report region it filters and a search field on/off; per column of that report: facet on/off, label, type (checkboxes, ranges, star rating), values shown, exclude, ranges (`..1000; 1000..3000 = Middle; 3000..`), from/to and order |
576
+ | `facets` | The report region it filters and a search field on/off; per column of that report: facet on/off, label, type (checkboxes, ranges, star rating), values shown, exclude, ranges (`..1000; 1000..3000 = Middle; 3000..`), from/to, a chart of the counts (bar or pie) and order |
536
577
  | `smart_filters` | The same per-column facets, the suggestions per facet and the search field's placeholder |
537
578
  | `display_selector` | Tabs or a select list, "Show all", remember the choice; per other region of the page: in a tab and the tab name (saved in that region's settings) |
538
579
  | `ai_assistant` | The AI service (those the application may use), system prompt, welcome text, placeholder, error message, tool rounds per question, questions per conversation, public or not, and the context queries and tools as JSON (checked when saving: query shapes, parameter types, REST data sources and authorization schemes that exist) ([AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant)) |
@@ -592,6 +633,12 @@ application, so the version it replaces becomes the newest backup and the restor
592
633
  same way. Backups belong to the installation: they are not exported, and an import over the
593
634
  application keeps them. Restores are logged in the activity log (`backup_restored`).
594
635
 
636
+ Workspace utilities → **Manage backups** (`/builder/backups`; APEX: Manage Backups) shows the backups of
637
+ every application of the current workspace on one page: how many, how many are kept, the newest and
638
+ whether the application changed since, and their size, with a link to each application's Backups.
639
+ **Back up now** there makes a backup of every application of the workspace that keeps backups (by
640
+ default only of those changed since their newest backup), with a note.
641
+
595
642
  ## Availability and build status
596
643
 
597
644
  **Availability** (in an application's header; APEX: Application Availability and Build Status) says
@@ -1097,6 +1144,19 @@ you sign out. Tasks link to Quick SQL, blueprints, table descriptions, sample da
1097
1144
  has (commented out), columns whose identity or generation differs, and types, domains and
1098
1145
  sequences that differ. The same from the command line (`pgkiln ddl`, `pgkiln compare`,
1099
1146
  [chapter 18](18-cli.md)) and for AI agents (`generate_ddl`, `compare_schemas`).
1147
+ - **Utilities** (`/builder/sql/utilities`; APEX: SQL Workshop → Utilities): tiles for Load and Unload Data, Sample
1148
+ Data, the Query Builder, DDL and Compare, and three pages of their own. Each runs in a read-only transaction with a
1149
+ 15-second statement timeout, on the SQL Workshop's connection (an isolated workspace's developers see only the
1150
+ schemas of its roles):
1151
+ - **Object Reports**: tables without a primary key, foreign keys without an index (with the `create index` to
1152
+ add), row-level security without policies, tables without row-level security (and to whom they are granted),
1153
+ privileges granted to PUBLIC, unused indexes, the largest tables and tables without a comment; each object links
1154
+ to the Object Browser. pgkiln's own `meta` schema and the system's are left out.
1155
+ - **About Database**: the PostgreSQL version, database size, uptime, a few settings (encoding, time zone,
1156
+ connections, memory, row security), the installed extensions and the schemas with their size.
1157
+ - **Database Monitor** (administrators only): the client sessions of this database from `pg_stat_activity`, active
1158
+ first, with their state, wait event, running time, the sessions that block them and the query (as far as the
1159
+ owner may read other roles' statistics). Read only: it cancels nothing.
1100
1160
 
1101
1161
  Because the SQL Workshop runs as the owner, restrict who gets a developer account.
1102
1162
 
@@ -137,7 +137,7 @@ database only checks the shape (at most 12 names of lower case letters, digits a
137
137
  | [`report`](#report-interactive-report) | Read-only table from a SELECT, with search, filters, sorting, control break, aggregates, highlights, computed columns, group by, pivot and chart views, row selection, saved reports, paging and CSV/Excel/PDF download |
138
138
  | [`grid`](#grid-interactive-grid) | Editable table on one database table, with aggregates, frozen, movable and resizable columns, saved grid reports, a row actions menu, master-detail, copy/paste of cells, and single row, group by and chart views |
139
139
  | [`form`](#form) | Fields for one row of a table, with automatic fetch and save |
140
- | [`chart`](#chart) | Bar, column, stacked, line, area, combo, scatter, bubble, donut, pie, gauge, funnel, radar, Gantt, pyramid or polar chart from a SELECT, with drill-down links |
140
+ | [`chart`](#chart) | Bar, column, stacked, line, area, combo, scatter, bubble, donut, pie, gauge, funnel, radar, Gantt, pyramid, polar, range, box plot or stock chart from a SELECT, with drill-down links |
141
141
  | [`cards`](#cards) | Cards or KPI tiles from a SELECT |
142
142
  | [`calendar`](#calendar) | Month, week, day and list views of dated rows, with create on click and drag and drop |
143
143
  | [`facets`](#facets-faceted-search) | Checkbox, range and star filters with counts and a search field for a report |
@@ -506,7 +506,7 @@ Attributes:
506
506
 
507
507
  | Key | Meaning |
508
508
  |---|---|
509
- | `kind` | `bar` (default), `column`, `stacked`, `line`, `area`, `combo`, `scatter`, `bubble`, `donut`, `pie`, `gauge`, `funnel`, `radar`, `gantt`, `pyramid` or `polar` |
509
+ | `kind` | `bar` (default), `column`, `stacked`, `line`, `area`, `combo`, `scatter`, `bubble`, `donut`, `pie`, `gauge`, `funnel`, `radar`, `gantt`, `pyramid`, `polar`, `range`, `boxplot` or `stock` |
510
510
  | `link` | Drill-down: `{"page": 2, "items": {"P2_DEPTNO": "#deptno#"}}` makes every data point a link (see below) |
511
511
  | `gauge` | For `gauge`: `{"min": 0, "max": 120, "warning": 80, "critical": 100}` (all optional) |
512
512
  | `format_mask` | A number format mask for the values in labels, tips and the data table, e.g. `"FML999G990"` (see [number formats](14-globalization.md#number-formats)) |
@@ -528,6 +528,9 @@ Attributes:
528
528
  | `gantt` | tasks over time | **label, start, end** (dates or timestamps; an empty end, or one equal to the start, is a milestone ◆), then optional columns **by name**: `progress` (0 to 100, the filled part of the bar), `task_id` and `depends_on` (the ids a task waits for: `3`, `3,4` or an array). A time axis in hours, days, weeks, months or years, a dashed line for now (in the session's time zone, like the timestamps the query returns), and elbow lines from the end of each predecessor to the start of the task. Rows without a start are left out |
529
529
  | `pyramid` | levels of a hierarchy, or two groups compared per band | **one series**: a triangle cut into segments from the top (the first row) down, each segment's **area** in proportion to its value (≤ 8; more fold into "Other"). **Two series**: back-to-back bars (a population pyramid), the first series to the left, both on one scale; negative values count as their size |
530
530
  | `polar` | values per period or direction (months, weekdays) | a polar area chart: **one equal sector per row** (up to 24) clockwise from 12 o'clock, the radius in proportion to the value on rings from zero; several series share a row's sector |
531
+ | `range` | a span per category (lowest and highest, from and to) | **low and high** columns (in either order): a floating column from one to the other; each further pair of columns is another range per row (up to four, with a legend). The axis doesn't have to start at zero |
532
+ | `boxplot` | the spread of values per group | **one value column with a row per observation** (the label repeats; the statistics are computed per label, in the order the labels first appear, like `percentile_cont`; empty values are left out), or **five columns** per row: minimum, first quartile, median, third quartile and maximum. A whisker from minimum to maximum, a box between the quartiles and a line at the median; the data table shows the five statistics (and the tip the number of values) |
533
+ | `stock` | prices over time | **open, high, low and close** columns in that order (any names): a candle per row, filled green when it closed at or above its opening and red when below, with a wick from low to high. The axis doesn't have to start at zero |
531
534
 
532
535
  A stacked chart, a combination of columns and a line, and a scatter plot:
533
536
 
@@ -573,7 +576,7 @@ select initcap(job) as job,
573
576
  from hr.emp group by job order by 1
574
577
  ```
575
578
 
576
- Gantt, pyramid and polar charts (HR page 32 "Project plan"):
579
+ Gantt, pyramid, polar, range, box plot and stock charts (HR page 32 "Project plan"):
577
580
 
578
581
  ```sql
579
582
  -- gantt: progress, task_id and depends_on by name; deptno only for the link
@@ -593,13 +596,25 @@ select b.band as "Salary",
593
596
  select to_char(make_date(2000, m, 1), 'Mon') as month, count(e.empno) as "Hires"
594
597
  from generate_series(1, 12) m left join hr.emp e on extract(month from e.hiredate) = m
595
598
  group by m order by m
599
+
600
+ -- range: lowest and highest salary per department (deptno only for the link)
601
+ select d.dname as "Department", min(e.sal) as "Lowest", max(e.sal) as "Highest", d.deptno
602
+ from hr.dept d join hr.emp e on e.deptno = d.deptno group by d.deptno, d.dname order by d.dname
603
+
604
+ -- box plot: one row per employee; a box per job
605
+ select e.job as "Job", e.sal as "Salary" from hr.emp e where e.active order by e.job
606
+
607
+ -- stock: open, high, low and close per day
608
+ select to_char(day, 'DD Mon') as "Day", open as "Open", high as "High", low as "Low", close as "Close"
609
+ from sales.share_price order by day
596
610
  ```
597
611
 
598
612
  A Gantt chart reads dates as their wall clock (a `timestamptz` in the session's time zone), shows a
599
613
  time only when it is not midnight, and has its own data table (label, start, end, progress,
600
614
  dependencies). Its rows count toward the chart's row limit (`max_rows`, default 1000).
601
615
 
602
- Up to 8 series; two or more get a legend. Every chart has hover/focus **tooltips** and a
616
+ Up to 8 series; two or more get a legend. When an axis has many labels, only some are shown, and in a
617
+ narrow chart (a region of a few columns, a phone) every other one of those. Every chart has hover/focus **tooltips** and a
603
618
  **Data table** toggle (the accessible alternative). Colours come from a palette checked for
604
619
  colour-vision deficiency, in light and dark mode; the gauge's status colours are reserved for
605
620
  status and always come with an icon and a label. Charts resize with the screen.
@@ -619,7 +634,8 @@ Columns that only the link refers to (here `deptno`) are **not drawn** as a seri
619
634
  can return a key next to the label. What links: bars, columns and stacked segments (per series),
620
635
  line and area points, scatter dots and bubbles, gauge dials, funnel stages, radar axis labels, pie
621
636
  and donut slices and their legend entries (not "Other"), Gantt bars and milestones (the whole row:
622
- `#series#` is empty), pyramid segments and bars, and polar sectors and their labels. The marks are for the mouse; from the
637
+ `#series#` is empty), pyramid segments and bars, polar sectors and their labels, range columns (per pair of
638
+ series when there are several), boxes (the first row of their label) and candles (the whole row). The marks are for the mouse; from the
623
639
  keyboard the **data table** has the same links (the labels, or each value when there are several
624
640
  series), and so do the radar and polar labels and the donut and pyramid legends.
625
641
 
@@ -732,6 +748,11 @@ Counts take the search and all *other* facets into account. Three kinds of facet
732
748
  With `"search": true` the panel starts with a search field: the same search as the report's own
733
749
  (`r<id>_q`, the row as text contains the term).
734
750
 
751
+ **Facet charts** (APEX: facet charts): with `"chart": "bar"` or `"chart": "pie"` a facet shows its counts as a chart,
752
+ collapsed under its heading (*Chart*). Each bar or slice, and each row of the chart's data table, links to the page
753
+ filtered on that value (a checkbox facet's value, or a range), the other filters kept. Values with no rows are left out
754
+ of the chart. In the HR sample, page 21 has a bar chart of jobs and a pie chart of salary ranges.
755
+
735
756
  Attributes:
736
757
 
737
758
  ```json
@@ -758,6 +779,7 @@ Attributes:
758
779
  | `facets[].ranges` | range: `[{"from": …, "to": …, "label": …}]`, numbers or `YYYY-MM-DD` dates; either bound may be left out (at most 20) |
759
780
  | `facets[].custom` | range: `true` adds *from*/*to* fields (default: only when there are no `ranges`) |
760
781
  | `facets[].max` | star: the highest rating, 2–10 (default 5) |
782
+ | `facets[].chart` | `bar` or `pie`: a chart of the facet's counts under it (faceted search only) |
761
783
 
762
784
  Only filters that a facet on the page allows are read from the URL: a value for a column without a
763
785
  facet, a range that isn't one of the facet's own, or a *from*/*to* on a facet without `custom`
@@ -827,6 +827,7 @@ application ask someone to approve, reject or complete something, and act on the
827
827
  | Business administrator (role) | Sees every task of this definition, delegates and cancels them |
828
828
  | The person who requested it may complete it | Off by default: nobody approves their own request |
829
829
  | Priority, Due in | 1 (urgent) – 5 (low); e.g. `2 days`. Overdue tasks are marked |
830
+ | When overdue, Maximum renewals, Escalation role | The expiration policy ([below](#overdue-tasks-expire-renew-escalate)); empty: overdue tasks stay open |
830
831
  | Details page, Details item | The subject links to this page, setting the item to the record key |
831
832
  | On completion (SQL) | Runs as the application's role when the task is approved, rejected or completed (below) |
832
833
 
@@ -858,6 +859,14 @@ user may do:
858
859
 
859
860
  Every step is in the task's history.
860
861
 
862
+ **Vacation rules** (APEX: vacation rules): a task list of *my tasks* has a **Vacation rule** section where users name
863
+ the user who stands in for them, from one day until another (at most a year ahead). While the rule is active, the
864
+ stand-in may act on, claim, release and delegate the tasks the user may act on as a **named owner** (the users given
865
+ to `meta.create_task`) or as the **actual owner**, and sees them in their own task list; the decision is recorded under
866
+ the stand-in's name. The rule is checked on every request, so the stand-in's access ends with it. Owners by **role**
867
+ are not passed on (the role's other members are there for that). From SQL: `meta.set_vacation_rule(delegate, from,
868
+ until)`, `meta.clear_vacation_rule()` and `meta.vacation_rule()` (the signed-in user's own rule).
869
+
861
870
  **4. On completion**, the definition's SQL runs **as the application's role, in the same
862
871
  transaction** as the decision, with these binds: `:TASK_ID`, `:DETAIL_PK`, `:OUTCOME`
863
872
  (`APPROVED`, `REJECTED` or `COMPLETED`), `:COMMENT`, `:APPROVER`, `:INITIATOR`, and the task
@@ -872,6 +881,22 @@ When a record is decided somewhere else (for example with buttons on its own pag
872
881
  open tasks so they don't linger: `select meta.close_tasks('EXPENSE_APPROVAL', :P5_ID::text,
873
882
  'approved')` (no completion SQL runs).
874
883
 
884
+ #### Overdue tasks: expire, renew, escalate
885
+
886
+ A task definition with *Due in* can say what happens to a task that is still open after its due date (APEX:
887
+ expiration policy). The server checks every minute:
888
+
889
+ | When overdue | What happens |
890
+ |---|---|
891
+ | (empty) | Nothing: the task stays open and is marked overdue |
892
+ | `expire` | The task ends with the state **expired** and no outcome; the completion SQL doesn't run |
893
+ | `renew` | The due date moves on by *Due in*, up to *Maximum renewals* times (default 3); then it expires |
894
+ | `escalate` | Once: the *Escalation role* becomes a potential owner, the priority 1, the task unassigned (its actual owner loses it) with a new due date; overdue again, it expires |
895
+
896
+ Each step is in the task's history, by `system`. A [workflow](#workflows) waiting for a task that expires goes on with
897
+ the outcome `expired` (`"next": {"approved": "…", "expired": "…"}`); without an `expired` branch it ends, like a
898
+ workflow whose task is cancelled.
899
+
875
900
  **SQL API** (application code; `meta.app_user()` is the acting user):
876
901
 
877
902
  | Function | |
@@ -889,7 +914,8 @@ reminds owners of overdue tasks; or put a task list on the home page.
889
914
 
890
915
  **Example:** in the HR sample application, a leave request creates a `LEAVE_APPROVAL` task for the
891
916
  employee's manager; *My tasks* (page 14) approves or rejects it through `hr.decide_leave`, the same
892
- function as the leave request page's buttons, and a request decided there closes its task.
917
+ function as the leave request page's buttons, and a request decided there closes its task. A leave request nobody
918
+ decides on within two days escalates to the administrators; managers going on holiday set a vacation rule on page 14.
893
919
 
894
920
  ## Workflows
895
921