pgkiln 0.51.0 → 0.52.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 (89) hide show
  1. package/SECURITY.md +5 -0
  2. package/db/migrations/121_ai_agents.sql +94 -0
  3. package/db/migrations/122_team_development.sql +186 -0
  4. package/db/migrations/123_workflow_outcomes.sql +407 -0
  5. package/db/migrations/124_component_details.sql +51 -0
  6. package/docs/apex-feature-parity.md +12 -10
  7. package/docs/guide/01-installation.md +7 -3
  8. package/docs/guide/03-builder.md +121 -7
  9. package/docs/guide/04-pages-and-regions.md +71 -21
  10. package/docs/guide/05-items.md +7 -1
  11. package/docs/guide/06-processing.md +123 -8
  12. package/docs/guide/07-dynamic-actions.md +34 -0
  13. package/docs/guide/09-reference.md +2 -1
  14. package/docs/guide/11-from-apex.md +2 -1
  15. package/docs/guide/12-development.md +44 -8
  16. package/docs/guide/20-ai-agents.md +4 -2
  17. package/examples/hr/README.md +2 -0
  18. package/examples/hr/hr_64_ai_agent.sql +97 -0
  19. package/examples/hr/hr_65_ask_reports.sql +21 -0
  20. package/examples/hr/hr_66_team_development.sql +58 -0
  21. package/examples/hr/hr_67_workflow_outcomes.sql +113 -0
  22. package/examples/hr/hr_68_component_details.sql +96 -0
  23. package/package.json +1 -1
  24. package/public/app.css +40 -0
  25. package/public/app.js +134 -20
  26. package/public/builder.css +83 -1
  27. package/public/builder.js +47 -0
  28. package/src/ai/chat.ts +18 -18
  29. package/src/ai/service.ts +35 -11
  30. package/src/ai/types.ts +7 -2
  31. package/src/app.ts +6 -0
  32. package/src/builder/advisor.ts +6 -3
  33. package/src/builder/ai-agents.ts +372 -0
  34. package/src/builder/ai-builder.ts +3 -3
  35. package/src/builder/ai.ts +13 -9
  36. package/src/builder/assistant.ts +22 -7
  37. package/src/builder/components.ts +38 -9
  38. package/src/builder/designer.ts +8 -3
  39. package/src/builder/feedback.ts +6 -1
  40. package/src/builder/locks.ts +7 -1
  41. package/src/builder/region-help.ts +2 -2
  42. package/src/builder/routes.ts +4 -1
  43. package/src/builder/shared.ts +1 -1
  44. package/src/builder/team.ts +991 -0
  45. package/src/builder/ui.ts +2 -1
  46. package/src/builder/workflow-migrate.ts +210 -0
  47. package/src/builder/workflows.ts +29 -6
  48. package/src/builder/workspace-home.ts +2 -0
  49. package/src/builder/workspace-transfer.ts +1 -1
  50. package/src/cli/replace.ts +7 -1
  51. package/src/i18n/ar.ts +17 -0
  52. package/src/i18n/cs.ts +17 -0
  53. package/src/i18n/da.ts +17 -0
  54. package/src/i18n/de.ts +17 -0
  55. package/src/i18n/el.ts +17 -0
  56. package/src/i18n/es.ts +17 -0
  57. package/src/i18n/fi.ts +17 -0
  58. package/src/i18n/fr.ts +17 -0
  59. package/src/i18n/he.ts +17 -0
  60. package/src/i18n/it.ts +17 -0
  61. package/src/i18n/ja.ts +17 -0
  62. package/src/i18n/ko.ts +17 -0
  63. package/src/i18n/nb.ts +17 -0
  64. package/src/i18n/pl.ts +17 -0
  65. package/src/i18n/pt.ts +17 -0
  66. package/src/i18n/ru.ts +17 -0
  67. package/src/i18n/sv.ts +17 -0
  68. package/src/i18n/tr.ts +17 -0
  69. package/src/i18n/uk.ts +17 -0
  70. package/src/i18n/zh.ts +17 -0
  71. package/src/i18n.ts +34 -0
  72. package/src/instance.ts +2 -0
  73. package/src/metadata.ts +9 -3
  74. package/src/runtime/ai-agents.ts +365 -0
  75. package/src/runtime/ai-filter.ts +385 -107
  76. package/src/runtime/assistant.ts +64 -11
  77. package/src/runtime/context.ts +2 -0
  78. package/src/runtime/ir-phrases.ts +812 -0
  79. package/src/runtime/ir-plan.ts +449 -0
  80. package/src/runtime/items.ts +1 -1
  81. package/src/runtime/regions.ts +6 -2
  82. package/src/runtime/render.ts +1 -1
  83. package/src/runtime/report.ts +1 -1
  84. package/src/runtime/tasks.ts +63 -12
  85. package/src/task-events.ts +232 -0
  86. package/src/team-samples.ts +74 -0
  87. package/src/team.ts +342 -0
  88. package/src/workflow.ts +135 -18
  89. package/src/workspace-export.ts +16 -1
@@ -37,6 +37,7 @@ your own from a static application file.
37
37
  | `selection_change` | Rows are selected in a report with [row selection](04-pages-and-regions.md#row-selection), or another master row in a grid; the condition sees the selected values, colon separated | Region ids or static ids |
38
38
  | `before_submit` | The page is about to be submitted; a declined `confirm` keeps the page | – |
39
39
  | `custom` | JavaScript raised the event: `pgkiln.trigger('ORDER_SAVED', value)` (APEX: custom events, `apex.event.trigger`) | Event names (case-insensitive) |
40
+ | `menu_entry` | An entry `{"label": …, "action": "NAME"}` of a [menu button](06-processing.md#menu-buttons-and-badges) was chosen (APEX: Menu entry chosen); the condition sees the entry's name ([below](#menu-entry-chosen)) | Entry names, or menu button names (any of their entries) |
40
41
 
41
42
  **The actions of one event and trigger run one after the other, in sequence order** (APEX: the true
42
43
  actions of a dynamic action), each waiting for the one before (a server action for its answer). A
@@ -79,11 +80,44 @@ actions of a dynamic action), each waiting for the one before (a server action f
79
80
  | `print` | browser | The browser's print dialog |
80
81
  | `current_position` | browser | The device's position as `latitude,longitude` (5 decimals) into the first affected item |
81
82
  | `share` | browser | The device's share sheet with the page's title, `message` and address; where there is none, the address is copied |
83
+ | `wait_popup` | browser | Shows a modal "please wait" popup with `message` (default *Please wait…*) until `remove_wait_popup` or the next page (APEX: Show Wait Popup); [below](#wait-popup) |
84
+ | `remove_wait_popup` | browser | Removes the wait popup (APEX: Remove Wait Popup) |
82
85
 
83
86
  Server-side actions first store `items_to_submit` in session state, run as the application's
84
87
  database role like everything else, and check the page's and the dynamic action's authorization.
85
88
  The SQL itself never reaches the browser.
86
89
 
90
+ ## Wait popup
91
+
92
+ `wait_popup` covers the page with a small dialog (a spinner and `message`) while something slow
93
+ runs, so the user doesn't click twice; `remove_wait_popup` takes it away. Because the actions of
94
+ one event run in sequence, each waiting for the one before, two patterns cover most cases:
95
+
96
+ | Sequence | Event | Action | |
97
+ |---|---|---|---|
98
+ | 10 | `click` on `RECALC` (a submit button) | `wait_popup` | *Recalculating…*: the popup stays until the next page arrives |
99
+ | 10 | `click` on `CHECK` | `wait_popup` | *Checking…* |
100
+ | 20 | `click` on `CHECK` | `execute_sql` | the Ajax call (also `set_value`, `refresh_region`, `download`, a plug-in) |
101
+ | 30 | `click` on `CHECK` | `remove_wait_popup` | |
102
+
103
+ `before_submit` → `wait_popup` shows it for every submit of the page. A submit the browser refuses
104
+ (a required field is empty) removes it again, and so does going back to the page in the browser.
105
+ The popup is a modal `<dialog>`: the page below can't be clicked or focused, Escape doesn't close
106
+ it, its text is a status message that screen readers read out, and the page's `<main>` is
107
+ `aria-busy` while it shows. It follows the theme (light and dark) and needs no inline styles or
108
+ scripts. From JavaScript: `const done = pgkiln.waitPopup('Saving…'); … done();` (or
109
+ `pgkiln.removeWaitPopup()`).
110
+
111
+ ## Menu entry chosen
112
+
113
+ A [menu button](06-processing.md#menu-buttons-and-badges)'s entry can run dynamic actions instead
114
+ of linking or submitting: `{"label": "Export", "action": "EXPORT"}`. Dynamic actions with event
115
+ `menu_entry` and trigger `EXPORT` run when it is chosen (the menu closes first); a trigger with the
116
+ menu button's name runs for every action entry of that menu, and a client-side condition tests the
117
+ entry's name (`equals` `EXPORT`). Without JavaScript such an entry does nothing, like a button with
118
+ action `da`. The HR example's page 47 has an *Actions* menu whose *Count employees* entry shows a
119
+ wait popup around an Ajax call.
120
+
87
121
  ## Ajax callbacks
88
122
 
89
123
  A page process with point **ajax** (APEX: an On Demand process) runs only when JavaScript calls it by
@@ -481,9 +481,10 @@ navigation entries and application processes have the same `build_option` column
481
481
  | `push_key` | The VAPID key pair of an application with [push notifications](17-mobile.md#push-notifications): `public_key`, `private_key` (encrypted with `PGKILN_SECRET_KEY`). Not exported | no |
482
482
  | `push_subscription` | Devices with notifications on: `app_id`, `username`, `endpoint` (the push service URL), the device's keys `p256dh` and `auth`, `user_agent`, `created_at`, `last_sent_at`, `failures`. Not exported | no (through `meta.has_push_subscription`) |
483
483
  | `push_message` | [Push notifications from SQL](#push-notifications-from-sql): `app_id`, `username`, `title`, `body`, `url`, `tag`, `urgency`, `ttl_s`, `status`, `attempts`, `devices`, `delivered`, `message`, `requested_by`, times. Kept 7 days, not exported | no |
484
- | `ai_service` | [AI services](06-processing.md#generate-text-with-ai) of the installation: `name`, `provider` (`anthropic`, `openai`), `model`, `effort`, `refusal_fallback`, `max_tokens`, `timeout_s`, `base_url`, `api_key_enc` (encrypted with `PGKILN_SECRET_KEY`, write-only), `enabled`. Not exported | no |
484
+ | `ai_service` | [AI services](06-processing.md#generate-text-with-ai) of the installation: `name`, `provider` (`anthropic`, `openai`, `gemini`, `openai_compatible`: 121), `model`, `effort`, `refusal_fallback`, `max_tokens`, `timeout_s`, `base_url`, `api_key_enc` (encrypted with `PGKILN_SECRET_KEY`, write-only), `enabled`. Not exported | no |
485
485
  | `app_ai_service` | Which applications may use which AI service, with daily limits `max_requests` and `max_tokens` (null: no limit). Not exported | no (through `meta.ai_available`) |
486
486
  | `ai_usage` | One row per AI request: `at`, `app_id`, `page_no`, `username`, `service`, `provider`, `model`, `source`, `input_tokens`, `output_tokens`, `duration_ms`, `status`, `message` (an error class, never prompt or answer text). Not exported | no |
487
+ | `ai_tool_call` | (121) Every tool call of an [AI assistant or agent](03-builder.md#ai-agents): `at`, `app_id`, `page_no`, `region_id`, `username`, `agent`, `tool`, `type`, `arguments` (the model's, cut to 4 KB), `status` (`ok`, `error`, `refused`), `duration_ms`, `message`. Kept 30 days, not exported | no |
487
488
  | `ai_conversation` | [AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant) conversations: one per session and region (`messages`: the provider's history, `turns`: what the page shows); deleted with the session. Not exported | no |
488
489
  | `ai_table_note` | Descriptions of tables and columns for models ([App Builder AI](03-builder.md#app-builder-ai)): `schema_name`, `table_name`, `column_name` (`''` for the table), `note`. Installation data, not exported | no |
489
490
  | `blueprint` | Saved [blueprints](03-builder.md#creating-an-application-from-a-blueprint): `name`, `spec` (the JSON), `app_id` (the application last created from it), `created_by`. Builder data, not exported | no |
@@ -22,10 +22,11 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
22
22
  | Instance administration: install/upgrade log | Workspace utilities → Installation (administrators) |
23
23
  | `APEX_WEB_SERVICE.make_rest_request`, `g_status_code` | `meta.web_request(url, method, body, headers, credential)` or `meta.web_request_source(source, params)`, then `meta.web_response(id)`. **Asynchronous**: the server makes the request after the page process that queued it, or after the commit ([chapter 9](09-reference.md#web-requests-from-sql)) |
24
24
  | Workspace → Generative AI services; *Generate Text with AI* process and dynamic action; `APEX_AI.generate` | Workspace utilities → AI services (Claude or OpenAI; administrators); the `ai_generate` process and dynamic action, with structured outputs into items ([chapter 6](06-processing.md#generate-text-with-ai)); `meta.ai_generate(service, prompt, system, schema)` then `meta.ai_result(id)` ([chapter 9](09-reference.md#ai-requests-from-sql)) |
25
- | AI Assistant region; AI agents with tools; natural language to interactive report (NL2IR) | The `ai_assistant` region: a chat with context queries (RAG over your queries) and tools (SQL as the app role with bound arguments, REST data sources) ([chapter 4](04-pages-and-regions.md#ai_assistant-ai-assistant)); `"ai_filter"` on a report: *Ask in your own words* ([chapter 4](04-pages-and-regions.md#natural-language-filters-ask-in-your-own-words)) |
25
+ | AI Assistant region; AI agents with tools; AI interactive reports (natural language to interactive report) | The `ai_assistant` region: a chat with context queries (RAG over your queries) and tools (SQL as the app role with bound arguments, REST data sources) ([chapter 4](04-pages-and-regions.md#ai_assistant-ai-assistant)); `"ai_filter"` on a report: *Ask in your own words*, the request becomes the report's settings as editable chips, with or without an AI service ([chapter 4](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports)) |
26
26
  | App Builder / SQL Workshop AI Assistant (create pages, generate and explain SQL); describing tables for AI | The App Builder's AI service: *Create pages with AI* (proposals you confirm), SQL Workshop → AI (SQL from a question, shown not run; explain a query or error), *Describe tables* (`meta.ai_table_note`, optionally `COMMENT ON`) ([chapter 3](03-builder.md#app-builder-ai)) |
27
27
  | Blueprints (spec-driven development) | Create → From a blueprint: a JSON blueprint (tables, pages, menu, sample data), optionally drafted by AI, reviewed, then created in one transaction ([chapter 3](03-builder.md#creating-an-application-from-a-blueprint)) |
28
28
  | `APEX_DATA_PARSER.parse`, `get_columns` | `meta.parse_data(blob, …)` (rows with `cols` and `data`), `meta.parse_data_columns(blob, …)`; CSV/TSV and JSON, not Excel or XML ([chapter 9](09-reference.md#parsing-files-in-sql)) |
29
+ | Team Development (issues, labels, milestones, templates, feedback) | Builder → Team Development (`/builder/team`): issues per workspace with labels in groups, milestones, assignees, application and page links, Markdown comments with @mentions shown in the builder, history, saved views and a board; a set-up wizard with sample labels, templates and milestones; feedback and developer comments become issues. No e-mail and no GitHub synchronisation ([chapter 3](03-builder.md#team-development)) |
29
30
  | Workspace users (APEX accounts), Application Access Control | `meta.account` (Builder → Users), `meta.app_access` (Access control) |
30
31
  | Page access protection "Arguments must have checksum" | `protection = 'checksum'` (the default) |
31
32
  | Automatic row processing (DML) | Process type `form_dml` |
@@ -33,6 +33,10 @@ src/
33
33
  rows and columns from the response schema, write-back from collection and item paths, notes), catalog
34
34
  files, load/insert/replace, sourceFromService
35
35
  workspace-export.ts (sprint 67) a workspace's components as a file (pgkiln-workspace/1) and importWorkspace (one transaction)
36
+ team.ts (sprint 113) Team Development's data: workspaceDevelopers (members and administrators: assignees, @mentions),
37
+ issueHtml (markdownHtml + mentions), recordMentions, logIssue (history), teamSetup, teamCounts, pageIssues,
38
+ exportTeam / importTeam (the workspace export's "team" part)
39
+ team-samples.ts (sprint 113) the set-up wizard's samples per builder language (English; add a key for another)
36
40
  subscriptions.ts application types and subscriptions (migration 056): offers, subscribe, refresh, publish, in sync
37
41
  workingcopy.ts working copies (migration 055): create, three-way compare per component (base, main, copy),
38
42
  merge into the main application or refresh the copy, both through cli/replace.ts
@@ -55,7 +59,10 @@ src/
55
59
  customauth.ts custom authentication: the app's function or PL/pgSQL body (a pg_temp function) and post-authentication code, as the app's role
56
60
  remember.ts "Keep me signed in": rotating persistent sign-in tokens
57
61
  workflow.ts workflows: step checks, the runner with parallel branches (NOTIFY + polling), invoke_api steps
58
- (the call between two transactions, with a lease), Advisor references, the diagram
62
+ (the call between two transactions, with a lease), Advisor references, the diagram;
63
+ (sprint 114) send_mail and draft steps, switch cases on task outcomes, the diagram as a standalone SVG
64
+ task-events.ts (sprint 114) task events (complete, expire, fail: SQL, mail, start a workflow) and built-in task
65
+ notifications from e-mail templates: checks, the actions as the app role, the meta.task_hook outbox runner
59
66
  process-jobs.ts background execution chains: the job queue (SKIP LOCKED, NOTIFY + polling), running a job as the app role
60
67
  api.ts REST API tokens for PostgREST, API role checks
61
68
  accounts.ts account settings and the password policy
@@ -138,7 +145,10 @@ src/
138
145
  limits, the call, meta.ai_usage), requests.ts (meta.ai_generate from SQL: runPendingAi() after an sql
139
146
  process, aiRequestTick() in the scheduler, 24-hour purge), chat.ts (conversations with tools, migration
140
147
  061: chat() runs the tool loop on a provider-format history (Claude: streamed, strict tools, tool_choice
141
- auto, content appended unchanged; OpenAI: strict function tools), limits and usage per provider call)
148
+ auto, content appended unchanged; OpenAI: strict function tools), limits and usage per provider call;
149
+ (121) an agent's effort (Claude output_config.effort, OpenAI reasoning_effort) and output limit).
150
+ Providers (121): anthropic, openai, gemini (OpenAI-compatible endpoint, GEMINI_BASE_URL), openai_compatible
151
+ (Ollama, vLLM, Azure OpenAI; base URL required, key optional): service.ts serviceKey(), baseUrlFor(), providerLabel()
142
152
  debug.ts debug messages: DebugLog (levels, timed steps, NOTICEs of meta.debug from appTx), started in
143
153
  loadContext, stored after the response (onResponse hook → meta.debug_save), hourly purge
144
154
  icons.ts icon helper (sprite in public/icons.svg)
@@ -156,7 +166,7 @@ src/
156
166
  styles.ts base styles (BASE_STYLES: Iris, Lavender, Standard; baseStyleOf → html data-style), Theme Roller style variants: fixed lists (fonts, sizes, corners), parseStyle/appStyles checks, the request's
157
167
  style (user choice, default), themeCss() (only hex values and constants reach the CSS)
158
168
  template-options.ts template options: the fixed CSS class list per region and button, templateClasses() (unknown values ignored)
159
- regions.ts region shell + chart (drill-down links, gauge settings)/cards/dynamic dispatch with row limits, lazy placeholder and cache, buttons (menu buttons, badges)
169
+ regions.ts region shell + chart (drill-down links, gauge settings)/cards/dynamic dispatch with row limits, lazy placeholder and cache, buttons (menu buttons, badges; (124) menu entries {action} → data-menu-entry for menu_entry dynamic actions)
160
170
  report.ts, report-views.ts (group by, pivot, chart), compute.ts (computed column expressions), grid.ts (aggregates, row actions, Actions menu, single row view, layoutFromForm), grid-layout.ts (column layouts: clean, arrange, per user), master-detail.ts (signed master row selection, details), facets.ts (facetChart: a facet's counts as a chart, sprint 65), items.ts
161
171
  (report.ts: paging with row ranges and max_rows, keyset paging (keysetPlan, seekCondition, signed r<id>_k), pagerNav, streamed CSV/Excel downloads with a cursor;
162
172
  items.ts: lovOptions, searchLov/lovLookup for popup LOVs, served by POST /a/:alias/:page/lov/:item/search in routes.ts)
@@ -193,7 +203,8 @@ src/
193
203
  template-components.ts template components: template language (allow-list, directives, escaping), plug-in files, report column templates
194
204
  builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_media_list, ut_metric_card, ut_timeline)
195
205
  template-region.ts template_component region
196
- tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route
206
+ tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route;
207
+ (sprint 114) custom outcome buttons, the Fail event and recovery of a failing completion SQL
197
208
  data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
198
209
  reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report);
199
210
  (sprint 110) dataset sources (resolveSources), edit_authz / admin_authz / publishing, pins (a preference),
@@ -205,8 +216,20 @@ src/
205
216
  schemas from items, answers into items), aiInputs/aiOutputs for its dynamic action (route in routes.ts)
206
217
  assistant.ts AI assistant region (migration 061): config checks, tool schemas and argument checks, context queries
207
218
  and SQL tools as the app role (rolled back unless "writes"), REST tools, meta.ai_conversation per
208
- session, formatAnswer (escaped), send/clear routes
209
- ai-filter.ts natural-language filters on a report ("ai_filter"): columns → structured output → checked → r<id>_f/q/s/d
219
+ session, formatAnswer (escaped), send/clear routes; (121) "agent": an AI agent's service, prompt, tools,
220
+ steps and authorization; links of navigate tools under the answer, browser actions once (__AIACT<id>)
221
+ ai-agents.ts (121) AI agents and AI tools (meta.app.ai_agents / ai_tools): checks (agentProblems, aiToolProblems,
222
+ definitionProblems), agentFor(), prepareTools() (authorization schemes, navigate pages the user may
223
+ open, strict schemas), runAgentTool() (sql: read-only transaction as the app role with a time limit;
224
+ function: named arguments, committed; rest; javascript: queued for the browser; navigate: URL and
225
+ checksum built here), logToolCall() (meta.ai_tool_call, debug level 4, 30-day purge)
226
+ ai-filter.ts "Ask in your own words" on a report ("ai_filter", sprint 112: AI interactive reports): the box, the chips
227
+ form (Edit/Remove/Undo, undo history in the session), ask and chips routes; an AI service's structured
228
+ output (planSchema) or the phrase parser, then ir-plan.ts
229
+ ir-plan.ts what a question may set: checkPlan (shown columns, whitelists, computeSql), applyPlan (→ the report's
230
+ URL parameters), chipsOf/editChip/removeChip (a chip = one URL parameter value, edits checked again)
231
+ ir-phrases.ts the rule-based parser without an AI service: phrase tables per language (en, nl, de), clauses →
232
+ a RawPlan; values looked up among the report's rows through a Lookup (query parameter)
210
233
  pdf.ts report PDFs with report layouts (pdfkit); rows from a cursor in batches (tablePdf takes batches)
211
234
  search.ts search region and search configurations (migration 080): words as ILIKE parameters, table/sql/list
212
235
  sources as the app role in savepoints, marked results, configColumns/searchConfigProblems for the Advisor
@@ -256,6 +279,11 @@ src/
256
279
  availability.ts Application → Availability (migration 083): status, message, users, build status (Run only is
257
280
  enforced by locks.ts blockingLock); availabilityBadge() in the application header
258
281
  feedback.ts Application → Feedback (migration 084): the setting, the list with filters, status and answers
282
+ team.ts (sprint 113, migration 122) Builder → Team Development /builder/team: issues (list with filters and saved views,
283
+ board by label group or milestone with a Move form per card, issue page with Markdown, comments, canned
284
+ responses, @mentions, history), labels and groups, milestones, templates, the set-up wizard; Make an issue
285
+ from feedback and developer comments; pageIssuesNote (Page Designer), appIssues (App Home), teamHomeBlock
286
+ (Workspace home). Every id from a request is looked up in the current workspace
259
287
  region-help.ts the Attributes (JSON) help per region type, with links to the guide
260
288
  mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
261
289
  applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
@@ -337,6 +365,8 @@ src/
337
365
  workspace's applications, the module › template › handler tree, handler forms, Try it (inject with a
338
366
  short-lived token), Run only / lock checks
339
367
  workflows.ts workflow versions, diagram and instances (Shared Components)
368
+ workflow-migrate.ts (sprint 114) migrating running workflow instances to a newer version (map, preview, apply with
369
+ meta.migrate_workflows) and the diagram as an SVG file (PNG in builder.js)
340
370
  process-jobs.ts page designer: the Jobs tab of a background chain process
341
371
  template-spec.ts template component property form (Shared Components)
342
372
  websources.ts web credentials and REST data sources: property specs, secret status, Test, suggested columns,
@@ -359,13 +389,17 @@ src/
359
389
  ai-builder.ts App Builder AI (migration 062): the builder's AI service (meta.builder_ai), SQL Workshop → AI (SQL from a
360
390
  question, shown not run; explain), describe tables (meta.ai_table_note, COMMENT ON, AI drafts),
361
391
  create pages with AI (proposals checked by checkProposals, created with meta.generate_page)
362
- assistant.ts page designer: AI assistant settings and a report's "Ask in your own words" (AI service, placeholder)
392
+ assistant.ts page designer: AI assistant settings and a report's "Ask in your own words" (off, built-in phrases or an AI service, placeholder)
363
393
  ai.ts Workspace utilities → AI services (administrators: services, write-only encrypted keys, access and
364
394
  daily limits per app, Test, usage log) and Activity → AI usage per application
395
+ ai-agents.ts (121) Shared Components → AI Agents: the agent and tool forms (checked before saving, renames follow
396
+ into agents and regions), toolCallTable() (Activity → AI usage → Tool calls), agentOptions()
365
397
  supporting.ts supporting objects: review page, running the install/upgrade/deinstall scripts as the app's role in one transaction
366
398
  public/
367
399
  app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
368
- app.js client runtime: dialogs (dialog_closed actions), popup LOVs, dynamic actions (focus, classes, messages), grids (add/duplicate rows, master-detail refresh, move/resize columns, copy/paste of cell ranges), menus, lazy regions, maps (Leaflet layers, marker clusters, heat layer, layer legend, area/distance filter) (no inline JS)
400
+ app.js client runtime: dialogs (dialog_closed actions), popup LOVs (sprint 115: the next page loads on scroll through an
401
+ IntersectionObserver, ‹/More without one), dynamic actions (focus, classes, messages; sprint 115: wait_popup /
402
+ remove_wait_popup as a modal <dialog class="wait-popup">, pgkiln.waitPopup(), the menu_entry event), grids (add/duplicate rows, master-detail refresh, move/resize columns, copy/paste of cell ranges), menus, lazy regions, maps (Leaflet layers, marker clusters, heat layer, layer legend, area/distance filter) (no inline JS)
369
403
  code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies);
370
404
  (sprint 108) the current line, and read-only <pre data-code-view="sql|plpgsql|json|html"> highlighted;
371
405
  its colours are the --ce-* tokens in builder.css
@@ -426,6 +460,8 @@ test/
426
460
  rest-sources.test.ts REST data sources, web credentials, SSRF checks, invoke_api (mock service + HR page 23)
427
461
  rest-modules.test.ts (sprint 104) modules, templates, PL/pgSQL and media handlers, OpenAPI, the builder's tree and Try it
428
462
  workflow-invoke.test.ts workflow invoke_api steps: the call between transactions, faults, retry, lease, Advisor, export (mock service)
463
+ workflow-outcomes.test.ts (sprint 114) custom task outcomes, task events and notifications, fail recovery, send_mail and draft
464
+ steps, migrating running instances, the SVG export (HR part 67: SALARY_CHANGE)
429
465
  large-tables.test.ts row ranges, max_rows, row limits, lazy regions, region caching, streamed downloads (HR page 25)
430
466
  grid.test.ts interactive grid: aggregates, layouts per user, saved grid reports, master-detail, row actions (HR page 27)
431
467
  custom-auth.test.ts custom authentication: function body, named function, post-authentication code, builder settings
@@ -7,8 +7,10 @@ it exports the application as files, edits them with its own file tools, shows t
7
7
  and imports them again. Laravel developers know the idea from Laravel Boost.
8
8
 
9
9
  This is separate from the AI features *inside* applications and the builder (AI assistant regions,
10
- *Generate with AI*): those call a model from pgkiln; here an agent on the developer's machine calls
11
- pgkiln.
10
+ [AI agents with tools](03-builder.md#ai-agents) in Shared Components, *Generate with AI*): those call a
11
+ model from pgkiln; here an agent on the developer's machine calls pgkiln. An application's AI agents
12
+ and tools are part of its files (`app.json`: `ai_agents`, `ai_tools`), so a coding agent can read and
13
+ change them like any other component.
12
14
 
13
15
  ## Setting it up
14
16
 
@@ -40,6 +40,8 @@ Then open http://127.0.0.1:3100/a/hr and sign in as `king`, `blake`, `jones`, `a
40
40
  | `hr_47_plugins.sql` | *Plug-ins* (page 40): the four example plug-ins of `examples/plugins/` (a region, an item, a dynamic action and a process plug-in) |
41
41
  | `hr_48_map_tiles.sql` | *Weather stations* (page 41): 20,000 generated stations (`hr.weather_station`) as vector tiles, and the stations above 2,000 m loaded for the visible area |
42
42
  | `hr_49_push_notifications.sql` | Push notifications: the app may send them (users turn them on under *My account → Notifications*); a new leave request notifies the manager, a decision notifies the employee, and the notification opens the request (page 7) |
43
+ | `hr_64_ai_agent.sql` | *HR agent* (page 46): the AI agent `HR_AGENT` (Shared Components → AI Agents) with four tools: `top_earners` (SQL, read-only as `hr_app`), `open_employee` (navigate: a link to the employee form with its checksum, only for users who may open page 3), `give_raise` (the PL/pgSQL function `hr.give_raise`, managers only) and `note` (a JavaScript tool: the custom event `hr_agent_note`). Ask *Who in Sales earns most?* Needs an AI service named `HR_ASSISTANT` (see part 41) |
44
+ | `hr_68_component_details.sql` | *Component details* (page 47): a popup LOV over 6,000 rows that loads more as the dialog scrolls, a wait popup around a two-second process (*Recalculate*) and around an Ajax call (*Check in the background*), and an *Actions* menu whose entries run dynamic actions (event `menu_entry`) |
43
45
 
44
46
  The files run in order, each once (recorded in `public.pgkiln_seed`), so new ones can be added
45
47
  later. pgkiln's own tests use this application as their fixture (`npm test` installs it first).
@@ -0,0 +1,97 @@
1
+ -- =====================================================================
2
+ -- HR example, part 64: an AI agent with tools (sprint 111;
3
+ -- docs/guide/03-builder.md, "AI agents"; APEX 26.1: AI Agents)
4
+ --
5
+ -- Shared Components → AI Agents:
6
+ -- - Tools:
7
+ -- top_earners (sql) employees by salary, highest first,
8
+ -- optionally of one department: a query
9
+ -- run as hr_app (row level security
10
+ -- applies) in a read-only transaction
11
+ -- open_employee (navigate) a link to the employee form (page 3)
12
+ -- with P3_EMPNO: pgkiln offers it only
13
+ -- to users who may open page 3
14
+ -- (MANAGER) and builds the checksum
15
+ -- give_raise (function) hr.give_raise(p_empno, p_pct): changes
16
+ -- data, only offered to managers
17
+ -- note (javascript) shows a message in the browser after
18
+ -- the answer (the custom event
19
+ -- "hr_agent_note" on page 46)
20
+ -- - Agent HR_AGENT: the AI service HR_ASSISTANT, a system prompt, low
21
+ -- reasoning effort, the four tools, at most 4 steps per question.
22
+ --
23
+ -- Page 46 "HR agent": an AI assistant region that names the agent. Ask
24
+ -- e.g. "Who in Sales earns most?": the agent calls top_earners and offers
25
+ -- "Open BLAKE's record", a link to page 3 with a valid checksum.
26
+ --
27
+ -- Like parts 41 and 42 the example creates no AI service: an administrator
28
+ -- adds one named HR_ASSISTANT under App Builder → Workspace utilities → AI
29
+ -- services (Claude, OpenAI, Gemini or an OpenAI-compatible endpoint) and
30
+ -- allows the HR application to use it.
31
+ -- =====================================================================
32
+
33
+ update meta.app set
34
+ ai_tools = $j$[
35
+ {"name": "top_earners", "type": "sql",
36
+ "description": "Active employees with their job, department and salary, highest salary first; optionally only one department (case-insensitive name, e.g. SALES).",
37
+ "sql": "select empno, ename, job, dname, sal from hr.staff_v where active and (:DNAME is null or dname ilike :DNAME) order by sal desc, ename",
38
+ "parameters": {"DNAME": {"type": "string", "optional": true, "description": "A department name, e.g. SALES."}},
39
+ "max_rows": 20, "timeout_s": 10},
40
+ {"name": "open_employee", "type": "navigate",
41
+ "description": "Offer the user a link to an employee's record (the employee form). Use the employee number (empno) as P3_EMPNO and a label such as \"Open BLAKE's record\".",
42
+ "pages": [3]},
43
+ {"name": "give_raise", "type": "function",
44
+ "description": "Raise an employee's salary by a percentage. Only when the user clearly asks for it; repeat the employee and the percentage first.",
45
+ "function": "hr.give_raise",
46
+ "parameters": {"P_EMPNO": {"type": "integer", "description": "The employee number (empno)."},
47
+ "P_PCT": {"type": "number", "description": "The raise in percent, e.g. 5."}},
48
+ "authz": "MANAGER"},
49
+ {"name": "note", "type": "javascript", "action": "hr_agent_note",
50
+ "description": "Show the user a short confirmation message in the page, e.g. after a raise.",
51
+ "parameters": {"TEXT": {"type": "string", "description": "The message."}}}
52
+ ]$j$::jsonb,
53
+ ai_agents = $j$[
54
+ {"name": "HR_AGENT", "description": "Answers questions about salaries and opens employee records",
55
+ "service": "HR_ASSISTANT",
56
+ "system": "You are the HR agent of a small company, talking with &APP_USER.. Answer questions about employees, departments and salaries with the top_earners tool. When you name one employee, offer a link to their record with open_employee (the empno as P3_EMPNO). Be brief.",
57
+ "effort": "low", "tools": ["top_earners", "open_employee", "give_raise", "note"], "max_steps": 4, "max_tokens": 2000}
58
+ ]$j$::jsonb
59
+ where alias = 'hr';
60
+
61
+ insert into meta.page (app_id, page_no, name, title, parent_page)
62
+ select id, 46, 'HR agent', 'HR agent', 1 from meta.app where alias = 'hr';
63
+
64
+ insert into meta.region (page_id, seq, title, type, columns, template, source, condition, config)
65
+ select p.id, r.seq, r.title, r.type, r.cols, 'standard', r.source, r.cond, r.config::jsonb
66
+ from meta.page p join meta.app a on a.id = p.app_id, (values
67
+ (10, 'No AI service yet', 'static', 12,
68
+ '<p class="alert alert-info">No AI service is configured for this application yet. An administrator adds an AI service named <b>HR_ASSISTANT</b> under <i>App Builder → Workspace utilities → AI services</i> and allows the HR application to use it.</p>',
69
+ 'not meta.ai_available(''HR_ASSISTANT'')', '{}'),
70
+ (20, 'Ask the HR agent', 'ai_assistant', 8, null, 'meta.ai_available(''HR_ASSISTANT'')',
71
+ $j${"agent": "HR_AGENT",
72
+ "welcome": "Hello! Ask me who earns what, e.g. who in Sales earns most. I can open an employee's record for you.",
73
+ "placeholder": "e.g. Who in Sales earns most?",
74
+ "max_turns": 20,
75
+ "error_message": "The HR agent could not answer just now. Please try again later."}$j$)
76
+ ) as r (seq, title, type, cols, source, cond, config)
77
+ where a.alias = 'hr' and p.page_no = 46;
78
+
79
+ -- the javascript tool "note": a custom event the agent raises after its answer
80
+ insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, message)
81
+ select p.id, 10, 'Agent note', 'custom', 'hr_agent_note', 'show_success', 'The HR agent did what you asked.'
82
+ from meta.page p join meta.app a on a.id = p.app_id where a.alias = 'hr' and p.page_no = 46;
83
+
84
+ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
85
+ select id, 30, 'HR agent', 'users', 46 from meta.app where alias = 'hr';
86
+
87
+ insert into meta.translation (app_id, language, source, target)
88
+ select a.id, 'nl', t.source, t.target
89
+ from meta.app a, (values
90
+ ('HR agent', 'HR-agent'),
91
+ ('Ask the HR agent', 'Vraag het de HR-agent'),
92
+ ('Hello! Ask me who earns what, e.g. who in Sales earns most. I can open an employee''s record for you.',
93
+ 'Hallo! Vraag me wie wat verdient, bijv. wie in Sales het meest verdient. Ik kan ook de gegevens van een medewerker voor je openen.'),
94
+ ('e.g. Who in Sales earns most?', 'bijv. Wie verdient het meest in Sales?')
95
+ ) as t (source, target)
96
+ where a.alias = 'hr'
97
+ on conflict do nothing;
@@ -0,0 +1,21 @@
1
+ -- =====================================================================
2
+ -- HR example, part 65: "Ask in your own words" without an AI service
3
+ -- (docs/guide/04-pages-and-regions.md, "Ask in your own words")
4
+ --
5
+ -- Page 2 "Employees": the interactive report gets a question box with the
6
+ -- built-in phrases ("ai_filter": {} names no AI service). Try
7
+ -- sum of salary by department where job is clerk
8
+ -- salary over 2500, sort by hire date, newest first
9
+ -- only clerks hired after 1981
10
+ -- bonus is 10% of salary
11
+ -- highlight salary over 4000 in red
12
+ -- Each answer becomes the report's own settings (filters, sort, group by,
13
+ -- computed columns, …), shown as chips with Edit and Remove under the box;
14
+ -- Undo goes back a step. Page 38's staff list uses the HR_ASSISTANT AI
15
+ -- service when an administrator has added it, else the same phrases.
16
+ -- =====================================================================
17
+
18
+ update meta.region r
19
+ set config = r.config || '{"ai_filter": {"placeholder": "e.g. sum of salary by department where job is clerk"}}'
20
+ from meta.page p, meta.app a
21
+ where r.page_id = p.id and p.app_id = a.id and a.alias = 'hr' and p.page_no = 2 and r.type = 'report';
@@ -0,0 +1,58 @@
1
+ -- =====================================================================
2
+ -- HR example, part 66: Team Development (docs/guide/03-builder.md,
3
+ -- "Team Development")
4
+ --
5
+ -- What Builder → Team Development → Set up installs in the Default
6
+ -- workspace (the label groups Type and Priority, milestones 1.0 and 1.1,
7
+ -- a bug report template and a canned response), and one issue about the
8
+ -- HR application: "Salary rounding on page 12", a Bug of milestone 1.1,
9
+ -- assigned to admin. The Page Designer of page 12 shows it, and the
10
+ -- Workspace home counts it until it is closed. Issues are workspace data
11
+ -- (the workspace export), not part of the HR application's export.
12
+ -- =====================================================================
13
+
14
+ insert into meta.team_label_group (workspace_id, name, description, seq) values
15
+ (1, 'Type', 'What kind of work the issue is.', 10),
16
+ (1, 'Priority', 'How soon it should be done.', 20)
17
+ on conflict (workspace_id, lower(name)) do nothing;
18
+
19
+ insert into meta.team_label (workspace_id, group_id, name, color, description, seq)
20
+ select 1, g.id, l.name, l.color, l.description, l.seq
21
+ from (values ('Type', 'Bug', 'red', 'Something does not work as it should.', 10),
22
+ ('Type', 'Feature', 'blue', 'New functionality.', 20),
23
+ ('Type', 'Task', 'teal', 'Work to do that is neither a bug nor a feature.', 30),
24
+ ('Priority', 'High', 'orange', 'Do this first.', 10),
25
+ ('Priority', 'Medium', 'yellow', 'Do this soon.', 20),
26
+ ('Priority', 'Low', 'gray', 'Do this when there is time.', 30)) as l(grp, name, color, description, seq)
27
+ join meta.team_label_group g on g.workspace_id = 1 and g.name = l.grp
28
+ on conflict (workspace_id, lower(name)) do nothing;
29
+
30
+ insert into meta.team_milestone (workspace_id, name, description, due_date) values
31
+ (1, '1.0', 'The first release.', current_date + 30),
32
+ (1, '1.1', 'The first update after the release.', current_date + 90)
33
+ on conflict (workspace_id, lower(name)) do nothing;
34
+
35
+ insert into meta.team_template (workspace_id, kind, name, title, body, labels, seq) values
36
+ (1, 'issue', 'Bug report', 'Bug: ', E'## What happens\n\n\n## What should happen\n\n\n## Steps to reproduce\n\n1. \n2. \n', '{Bug}', 10),
37
+ (1, 'response', 'Thanks, we will look at it', null, 'Thank you for reporting this. We will look at it and keep this issue up to date.', '{}', 10)
38
+ on conflict (workspace_id, kind, lower(name)) do nothing;
39
+
40
+ with issue as (
41
+ insert into meta.team_issue (workspace_id, title, body, milestone_id, app_id, page_no, created_by)
42
+ select 1, 'Salary rounding on page 12',
43
+ E'The leave calendar shows daily salary costs rounded to whole euros; finance expects cents.\n\n'
44
+ || E'- Open **page 12** (Leave calendar)\n- Compare a day with the salary in `hr.emp`\n\n@admin can you have a look?',
45
+ (select id from meta.team_milestone where workspace_id = 1 and name = '1.1'),
46
+ a.id, 12, 'admin'
47
+ from meta.app a
48
+ where a.alias = 'hr'
49
+ and not exists (select 1 from meta.team_issue i where i.workspace_id = 1 and i.title = 'Salary rounding on page 12')
50
+ returning id
51
+ ), labelled as (
52
+ insert into meta.team_issue_label (issue_id, label_id)
53
+ select issue.id, l.id from issue join meta.team_label l on l.workspace_id = 1 and l.name in ('Bug', 'Medium')
54
+ ), assigned as (
55
+ insert into meta.team_issue_assignee (issue_id, username)
56
+ select issue.id, 'admin' from issue where exists (select 1 from meta.developer where username = 'admin')
57
+ )
58
+ insert into meta.team_issue_event (issue_id, username, action) select id, 'admin', 'created' from issue;
@@ -0,0 +1,113 @@
1
+ -- =====================================================================
2
+ -- HR example, part 67: custom task outcomes, task events and
3
+ -- notifications, a Send E-Mail step, drafts and versions of a workflow
4
+ -- (migration 123; docs/guide/06-processing.md, "Approvals" and "Workflows")
5
+ --
6
+ -- SALARY_DECISION is a task with three outcomes instead of Approve / Reject:
7
+ -- Yes, No and Undecided. Its owners (role admin) get an e-mail when a task
8
+ -- is created (notification SALARY_TASK); when one is completed an event
9
+ -- records the decision as a notification for the initiator.
10
+ --
11
+ -- SALARY_CHANGE asks for that decision and branches on its outcome:
12
+ -- 1. APPROVE the task (an administrator decides)
13
+ -- 2. DECIDE YES: apply the new salary; UNDECIDED: ask again; otherwise the end
14
+ -- 3. APPLY the new salary (SQL)
15
+ -- 4. TELL an e-mail to the employee (version 2: a send_mail step)
16
+ -- Version 1 had no TELL step and is inactive; version 2 is active; version 3
17
+ -- is in development with a draft step (BUDGET_CHECK) that is still a
18
+ -- placeholder: it is drawn dashed, and can't be activated until it is
19
+ -- replaced. Instances of version 1 can be migrated to version 2 under
20
+ -- Shared Components → Workflows → SALARY_CHANGE → Migrate running instances.
21
+ --
22
+ -- Start one from SQL (e.g. the SQL Workshop, signed in to the application):
23
+ -- select meta.start_workflow('SALARY_CHANGE', '7499', '{"ENAME": "Allen", "NEW_SAL": 1760}')
24
+ -- and decide on it under My tasks (page 14) as king.
25
+ -- =====================================================================
26
+
27
+ insert into meta.email_template (app_id, name, subject, html_body, text_body)
28
+ select id, 'SALARY_TASK', 'To decide: #SUBJECT#',
29
+ $h$<p>A salary change waits for your decision: <b>#SUBJECT#</b>.</p>
30
+ <p>Requested by #INITIATOR#. Decide under <i>My tasks</i> in HR Demo.</p>$h$,
31
+ $t$A salary change waits for your decision: #SUBJECT#.
32
+
33
+ Requested by #INITIATOR#. Decide under "My tasks" in HR Demo.$t$
34
+ from meta.app where alias = 'hr'
35
+ union all
36
+ select id, 'SALARY_CHANGED', 'Your salary changes to #NEW_SAL#',
37
+ $h$<p>Dear #ENAME#,</p><p>Your new salary of <b>#NEW_SAL#</b> was approved by #TASK_APPROVER#.</p><p>Kind regards,<br>HR</p>$h$,
38
+ $t$Dear #ENAME#,
39
+
40
+ Your new salary of #NEW_SAL# was approved by #TASK_APPROVER#.
41
+
42
+ Kind regards,
43
+ HR$t$
44
+ from meta.app where alias = 'hr';
45
+
46
+ insert into meta.task_definition (app_id, name, type, subject, owner_roles, admin_role, priority, due_in, outcomes, events, notifications)
47
+ select id, 'SALARY_DECISION', 'approval', 'Salary of &ENAME. to &NEW_SAL.', '{admin}'::text[], 'admin', 2, '5 days',
48
+ $o$[{"name": "YES", "label": "Yes", "colour": "success"},
49
+ {"name": "NO", "label": "No", "colour": "danger"},
50
+ {"name": "UNDECIDED", "label": "Undecided", "colour": "warning"}]$o$::jsonb,
51
+ $e$[{"on": "complete", "type": "sql", "code": "select hr.salary_decided(:DETAIL_PK::int, :OUTCOME, :INITIATOR)"}]$e$::jsonb,
52
+ '{"create": "SALARY_TASK"}'::jsonb
53
+ from meta.app where alias = 'hr';
54
+
55
+ -- Notifications are for other users, which RLS forbids the app: security definer, like the onboarding ones.
56
+ create function hr.salary_decided(p_empno int, p_outcome text, p_initiator text) returns void
57
+ language plpgsql security definer set search_path = hr, pg_catalog as $$
58
+ begin
59
+ insert into hr.notification (username, message)
60
+ select p_initiator, format('Salary change of %s: %s.', initcap(e.ename), lower(p_outcome))
61
+ from hr.emp e where e.empno = p_empno;
62
+ end
63
+ $$;
64
+ create function hr.salary_change_apply(p_empno int, p_sal numeric) returns timestamptz
65
+ language plpgsql security definer set search_path = hr, pg_catalog as $$
66
+ begin
67
+ if p_sal is null or p_sal <= 0 then
68
+ raise exception 'A salary is more than 0.';
69
+ end if;
70
+ update hr.emp set sal = p_sal where empno = p_empno;
71
+ return now();
72
+ end
73
+ $$;
74
+ -- the e-mail address of an employee's account (the accounts are pgkiln's, not the application's)
75
+ create function hr.employee_email(p_empno int) returns text
76
+ language sql stable security definer set search_path = hr, pg_catalog as $$
77
+ select a.email from hr.emp e join meta.account a on lower(a.username) = lower(e.username) where e.empno = p_empno and a.active
78
+ $$;
79
+ revoke all on function hr.salary_decided(int, text, text), hr.salary_change_apply(int, numeric), hr.employee_email(int) from public;
80
+ grant execute on function hr.salary_decided(int, text, text), hr.salary_change_apply(int, numeric), hr.employee_email(int) to hr_app;
81
+
82
+ -- version 1, then version 2 (with the e-mail) becomes active, then version 3 in development (a draft step)
83
+ insert into meta.workflow_definition (app_id, name, title, description, admin_role, steps)
84
+ select id, 'SALARY_CHANGE', 'Salary of &ENAME. to &NEW_SAL.', 'A salary change, decided by an administrator (Yes, No or Undecided).', 'admin',
85
+ $s$[
86
+ {"name": "APPROVE", "type": "task", "task": "SALARY_DECISION"},
87
+ {"name": "DECIDE", "type": "switch", "cases": [{"outcome": "YES", "next": "APPLY"}, {"outcome": "UNDECIDED", "next": "APPROVE"}], "otherwise": "END"},
88
+ {"name": "APPLY", "type": "sql", "code": "select hr.salary_change_apply(:DETAIL_PK::int, :NEW_SAL::numeric) as applied_at", "next": "END"},
89
+ {"name": "END", "type": "end"}
90
+ ]$s$::jsonb
91
+ from meta.app where alias = 'hr';
92
+
93
+ select meta.new_workflow_version(d.id) from meta.workflow_definition d join meta.app a on a.id = d.app_id where a.alias = 'hr' and d.name = 'SALARY_CHANGE';
94
+ update meta.workflow_definition d set dev_steps = $s$[
95
+ {"name": "APPROVE", "type": "task", "task": "SALARY_DECISION"},
96
+ {"name": "DECIDE", "type": "switch", "cases": [{"outcome": "YES", "next": "APPLY"}, {"outcome": "UNDECIDED", "next": "APPROVE"}], "otherwise": "END"},
97
+ {"name": "APPLY", "type": "sql", "code": "select hr.salary_change_apply(:DETAIL_PK::int, :NEW_SAL::numeric) as applied_at"},
98
+ {"name": "TELL", "type": "send_mail", "to": "select hr.employee_email(:DETAIL_PK::int)", "template": "SALARY_CHANGED", "next": "END"},
99
+ {"name": "END", "type": "end"}
100
+ ]$s$::jsonb
101
+ from meta.app a where a.id = d.app_id and a.alias = 'hr' and d.name = 'SALARY_CHANGE';
102
+ select meta.activate_workflow_version(d.id) from meta.workflow_definition d join meta.app a on a.id = d.app_id where a.alias = 'hr' and d.name = 'SALARY_CHANGE';
103
+
104
+ select meta.new_workflow_version(d.id) from meta.workflow_definition d join meta.app a on a.id = d.app_id where a.alias = 'hr' and d.name = 'SALARY_CHANGE';
105
+ update meta.workflow_definition d set dev_steps = $s$[
106
+ {"name": "BUDGET_CHECK", "type": "draft", "note": "check the department's budget", "next": "APPROVE"},
107
+ {"name": "APPROVE", "type": "task", "task": "SALARY_DECISION"},
108
+ {"name": "DECIDE", "type": "switch", "cases": [{"outcome": "YES", "next": "APPLY"}, {"outcome": "UNDECIDED", "next": "APPROVE"}], "otherwise": "END"},
109
+ {"name": "APPLY", "type": "sql", "code": "select hr.salary_change_apply(:DETAIL_PK::int, :NEW_SAL::numeric) as applied_at"},
110
+ {"name": "TELL", "type": "send_mail", "to": "select hr.employee_email(:DETAIL_PK::int)", "template": "SALARY_CHANGED", "next": "END"},
111
+ {"name": "END", "type": "end"}
112
+ ]$s$::jsonb
113
+ from meta.app a where a.id = d.app_id and a.alias = 'hr' and d.name = 'SALARY_CHANGE';