pgkiln 0.50.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 (104) hide show
  1. package/SECURITY.md +7 -0
  2. package/db/migrations/119_workspace_home.sql +30 -0
  3. package/db/migrations/120_datasets.sql +87 -0
  4. package/db/migrations/121_ai_agents.sql +94 -0
  5. package/db/migrations/122_team_development.sql +186 -0
  6. package/db/migrations/123_workflow_outcomes.sql +407 -0
  7. package/db/migrations/124_component_details.sql +51 -0
  8. package/docs/apex-feature-parity.md +15 -12
  9. package/docs/guide/01-installation.md +9 -3
  10. package/docs/guide/03-builder.md +211 -11
  11. package/docs/guide/04-pages-and-regions.md +96 -22
  12. package/docs/guide/05-items.md +7 -1
  13. package/docs/guide/06-processing.md +123 -8
  14. package/docs/guide/07-dynamic-actions.md +34 -0
  15. package/docs/guide/09-reference.md +2 -1
  16. package/docs/guide/11-from-apex.md +2 -1
  17. package/docs/guide/12-development.md +70 -11
  18. package/docs/guide/20-ai-agents.md +4 -2
  19. package/examples/hr/README.md +2 -0
  20. package/examples/hr/hr_63_datasets.sql +29 -0
  21. package/examples/hr/hr_64_ai_agent.sql +97 -0
  22. package/examples/hr/hr_65_ask_reports.sql +21 -0
  23. package/examples/hr/hr_66_team_development.sql +58 -0
  24. package/examples/hr/hr_67_workflow_outcomes.sql +113 -0
  25. package/examples/hr/hr_68_component_details.sql +96 -0
  26. package/package.json +1 -1
  27. package/public/app.css +46 -0
  28. package/public/app.js +134 -20
  29. package/public/builder.css +116 -0
  30. package/public/builder.js +47 -0
  31. package/src/ai/chat.ts +18 -18
  32. package/src/ai/service.ts +35 -11
  33. package/src/ai/types.ts +7 -2
  34. package/src/app.ts +11 -1
  35. package/src/builder/advisor.ts +6 -3
  36. package/src/builder/ai-agents.ts +372 -0
  37. package/src/builder/ai-builder.ts +3 -3
  38. package/src/builder/ai.ts +13 -9
  39. package/src/builder/assistant.ts +22 -7
  40. package/src/builder/components.ts +38 -9
  41. package/src/builder/datasets.ts +472 -0
  42. package/src/builder/designer.ts +8 -3
  43. package/src/builder/feedback.ts +6 -1
  44. package/src/builder/home.ts +12 -7
  45. package/src/builder/instance.ts +2 -0
  46. package/src/builder/locks.ts +7 -1
  47. package/src/builder/region-help.ts +2 -2
  48. package/src/builder/reporter.ts +52 -3
  49. package/src/builder/routes.ts +7 -4
  50. package/src/builder/shared.ts +1 -1
  51. package/src/builder/subscriptions.ts +1 -0
  52. package/src/builder/team.ts +991 -0
  53. package/src/builder/ui.ts +8 -5
  54. package/src/builder/workflow-migrate.ts +210 -0
  55. package/src/builder/workflows.ts +29 -6
  56. package/src/builder/workspace-home.ts +285 -0
  57. package/src/builder/workspace-transfer.ts +1 -1
  58. package/src/builder/workspaces.ts +7 -4
  59. package/src/cli/replace.ts +7 -1
  60. package/src/datasets.ts +367 -0
  61. package/src/i18n/ar.ts +33 -0
  62. package/src/i18n/cs.ts +33 -0
  63. package/src/i18n/da.ts +33 -0
  64. package/src/i18n/de.ts +33 -0
  65. package/src/i18n/el.ts +33 -0
  66. package/src/i18n/es.ts +33 -0
  67. package/src/i18n/fi.ts +33 -0
  68. package/src/i18n/fr.ts +33 -0
  69. package/src/i18n/he.ts +33 -0
  70. package/src/i18n/it.ts +33 -0
  71. package/src/i18n/ja.ts +33 -0
  72. package/src/i18n/ko.ts +33 -0
  73. package/src/i18n/nb.ts +33 -0
  74. package/src/i18n/pl.ts +33 -0
  75. package/src/i18n/pt.ts +33 -0
  76. package/src/i18n/ru.ts +33 -0
  77. package/src/i18n/sv.ts +33 -0
  78. package/src/i18n/tr.ts +33 -0
  79. package/src/i18n/uk.ts +33 -0
  80. package/src/i18n/zh.ts +33 -0
  81. package/src/i18n.ts +66 -0
  82. package/src/instance.ts +3 -0
  83. package/src/metadata.ts +9 -3
  84. package/src/release-check.ts +139 -0
  85. package/src/reporting-app.ts +101 -0
  86. package/src/runtime/ai-agents.ts +365 -0
  87. package/src/runtime/ai-filter.ts +385 -107
  88. package/src/runtime/assistant.ts +64 -11
  89. package/src/runtime/context.ts +2 -0
  90. package/src/runtime/data-reporter.ts +199 -32
  91. package/src/runtime/ir-phrases.ts +812 -0
  92. package/src/runtime/ir-plan.ts +449 -0
  93. package/src/runtime/items.ts +1 -1
  94. package/src/runtime/regions.ts +6 -2
  95. package/src/runtime/render.ts +1 -1
  96. package/src/runtime/report.ts +1 -1
  97. package/src/runtime/tasks.ts +63 -12
  98. package/src/session.ts +2 -0
  99. package/src/subscriptions.ts +2 -1
  100. package/src/task-events.ts +232 -0
  101. package/src/team-samples.ts +74 -0
  102. package/src/team.ts +342 -0
  103. package/src/workflow.ts +135 -18
  104. package/src/workspace-export.ts +39 -4
@@ -21,6 +21,9 @@ src/
21
21
  env.ts .env loader (imported first)
22
22
  migrate.ts applies db/migrations and examples (scripts/migrate.ts, pgkiln migrate); logs each run that applies
23
23
  instance.ts instance settings (meta.setting over environment variables, cached 30 s) and the configuration overview
24
+ release-check.ts (sprint 109) "pgkiln X is available": meta.setting 'release_check' over PGKILN_RELEASE_CHECK (off by default);
25
+ GitHub's releases/latest (PGKILN_RELEASE_URL in tests), 5 s timeout, 1 MB, cached a day per process,
26
+ never under node --test without PGKILN_RELEASE_URL; parseRelease keeps a semver and an https link only
24
27
  or fails a file in public.pgkiln_install_log
25
28
  appfiles.ts application export as one file per component (dir layout, static ids) and back
26
29
  blueprint.ts blueprints (migration 063): checkBlueprint (names, types, references, pages, sample rows), tableOrder,
@@ -30,6 +33,10 @@ src/
30
33
  rows and columns from the response schema, write-back from collection and item paths, notes), catalog
31
34
  files, load/insert/replace, sourceFromService
32
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)
33
40
  subscriptions.ts application types and subscriptions (migration 056): offers, subscribe, refresh, publish, in sync
34
41
  workingcopy.ts working copies (migration 055): create, three-way compare per component (base, main, copy),
35
42
  merge into the main application or refresh the copy, both through cli/replace.ts
@@ -52,7 +59,10 @@ src/
52
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
53
60
  remember.ts "Keep me signed in": rotating persistent sign-in tokens
54
61
  workflow.ts workflows: step checks, the runner with parallel branches (NOTIFY + polling), invoke_api steps
55
- (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
56
66
  process-jobs.ts background execution chains: the job queue (SKIP LOCKED, NOTIFY + polling), running a job as the app role
57
67
  api.ts REST API tokens for PostgREST, API role checks
58
68
  accounts.ts account settings and the password policy
@@ -77,6 +87,13 @@ src/
77
87
  datamodel.ts (sprint 98) Data Modeler diagrams: readModel() (catalog), boxSize(), fkPath() (crow's feet), autoLayout(), diagramSvg()
78
88
  changelog.ts (sprint 106, migration 118) the change log: isSchemaDdl(), ddlTarget() (schema and object), changeName()
79
89
  (add_bonus, create_index_x), recordChanges() (grouped per schema), Flyway / Liquibase / plain SQL writers
90
+ datasets.ts (sprint 110, migration 120) Data Reporter datasets: checkDataset() (names, joins to earlier tables, columns
91
+ and filters on its tables, whitelisted operators; with a catalog: existing objects), datasetSql() (quoted
92
+ identifiers, literal values), readSchemaCatalog() (tables, views, columns, foreign keys), proposeJoin() /
93
+ proposeDataset(), storeDataset(), appDatasets() (the runtime's read), pgkiln-dataset/1 files
94
+ reporting-app.ts (sprint 110) reporting applications: createReportingApp() (createApp, SELECT-only grants on the datasets'
95
+ objects, type reporting, roles viewer/editor/administrator as schemes EDITOR and ADMIN, page 1 a Data
96
+ Reporter), reportingApps(), reportingAppCount() (meta.reporting_app_count, for the workspace home)
80
97
  tabledef.ts (sprint 95) a table as a definition: readTable(), checkTable(), tableSql() (CREATE, or ALTER from the catalog's version)
81
98
  sqlformat.ts (sprint 93) formatSql(): the worksheet's Format (clauses per line, keyword case; strings and comments kept)
82
99
  sqlplan.ts (sprint 92) EXPLAIN (FORMAT JSON) as a tree: parsePlan() (self time, slowest step, misestimates), planText()
@@ -128,7 +145,10 @@ src/
128
145
  limits, the call, meta.ai_usage), requests.ts (meta.ai_generate from SQL: runPendingAi() after an sql
129
146
  process, aiRequestTick() in the scheduler, 24-hour purge), chat.ts (conversations with tools, migration
130
147
  061: chat() runs the tool loop on a provider-format history (Claude: streamed, strict tools, tool_choice
131
- 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()
132
152
  debug.ts debug messages: DebugLog (levels, timed steps, NOTICEs of meta.debug from appTx), started in
133
153
  loadContext, stored after the response (onResponse hook → meta.debug_save), hourly purge
134
154
  icons.ts icon helper (sprite in public/icons.svg)
@@ -146,7 +166,7 @@ src/
146
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
147
167
  style (user choice, default), themeCss() (only hex values and constants reach the CSS)
148
168
  template-options.ts template options: the fixed CSS class list per region and button, templateClasses() (unknown values ignored)
149
- 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)
150
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
151
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;
152
172
  items.ts: lovOptions, searchLov/lovLookup for popup LOVs, served by POST /a/:alias/:page/lov/:item/search in routes.ts)
@@ -183,9 +203,12 @@ src/
183
203
  template-components.ts template components: template language (allow-list, directives, escaping), plug-in files, report column templates
184
204
  builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_media_list, ut_metric_card, ut_timeline)
185
205
  template-region.ts template_component region
186
- 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
187
208
  data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
188
- reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report)
209
+ reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report);
210
+ (sprint 110) dataset sources (resolveSources), edit_authz / admin_authz / publishing, pins (a preference),
211
+ publish / pin routes, the stable URL GET /a/:alias/reports/:id
189
212
  form-designer.ts Form Designer region (migration 106): sources from the region's config, checkFormDef (offered columns, displays,
190
213
  actions), forms as cards, a form's rows, a row's form (lost update hash), the designer; row/design/delete routes
191
214
  workflows.ts workflow console region and its actions
@@ -193,8 +216,20 @@ src/
193
216
  schemas from items, answers into items), aiInputs/aiOutputs for its dynamic action (route in routes.ts)
194
217
  assistant.ts AI assistant region (migration 061): config checks, tool schemas and argument checks, context queries
195
218
  and SQL tools as the app role (rolled back unless "writes"), REST tools, meta.ai_conversation per
196
- session, formatAnswer (escaped), send/clear routes
197
- 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)
198
233
  pdf.ts report PDFs with report layouts (pdfkit); rows from a cursor in batches (tablePdf takes batches)
199
234
  search.ts search region and search configurations (migration 080): words as ILIKE parameters, table/sql/list
200
235
  sources as the app role in savepoints, marked results, configColumns/searchConfigProblems for the Advisor
@@ -217,7 +252,12 @@ src/
217
252
  routes.ts sign-in, app home, settings, activity, developers, create (POST, via newapp.ts)/import (POST)
218
253
  wizards.ts create page wizards: step 2 forms per page type (defaults from meta.wizard_defaults), POST → meta.generate_page
219
254
  (the generators are PL/pgSQL in migration 047: catalog, defaults, form/cards/calendar/chart/map/facets/master-detail)
220
- home.ts App Builder home (tiles, applications report/cards, Recent), Create, Import, Dashboard, Utilities
255
+ home.ts App Builder home at /builder/apps and /builder (tiles, applications report/cards, Recent; /builder redirects
256
+ to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities
257
+ workspace-home.ts (sprint 109, migration 119) the Workspace home /builder/home, opened after sign-in: tool cards (Data Reporter
258
+ when GET /builder/reporter exists), workspace message edited in place (workspaceAdmin), Summary, Top
259
+ applications / users / developers over meta.developer.home_timeframe, System message (src/release-check.ts),
260
+ POST timeframe, message, release-check; releaseCheckRegion for Instance settings
221
261
  newapp.ts creating an application (schema, role app_<alias>, Home page, first user): blank app, from a file, from tables
222
262
  appfromfile.ts Create → From a file: upload (src/dataload.ts parsing), proposed table/columns, one transaction:
223
263
  app + table + rows (loadRows) + pages (meta.generate_page: report and form, chart, facets)
@@ -239,6 +279,11 @@ src/
239
279
  availability.ts Application → Availability (migration 083): status, message, users, build status (Run only is
240
280
  enforced by locks.ts blockingLock); availabilityBadge() in the application header
241
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
242
287
  region-help.ts the Attributes (JSON) help per region type, with links to the guide
243
288
  mail.ts Workspace utilities → Mail services (administrators: providers, write-only secret, allowed senders,
244
289
  applications with From and daily limit, test) and Applications → Activity → Mail (retry, cancel)
@@ -305,7 +350,11 @@ src/
305
350
  pwa.ts Settings → Progressive Web App (icon upload)
306
351
  themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
307
352
  subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
308
- reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing)
353
+ reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing;
354
+ (sprint 110) a dataset as a source, who may edit, who may moderate, publishing)
355
+ datasets.ts (sprint 110) App Builder → Data Reporter (/builder/reporter): datasets (new: schema, tables; a dataset's
356
+ joins, columns, row filter, query and preview), files (download, export all, import), Create reporting
357
+ application
309
358
  form-designer.ts page designer: Form Designer settings (designers' scheme; sources: key, actions, offered columns, labels, lists of values)
310
359
  workingcopies.ts Working copies: list and create, compare with differences, merge or refresh with conflict choices, delete
311
360
  rest.ts REST module endpoints list and curl example (Shared Components)
@@ -316,6 +365,8 @@ src/
316
365
  workspace's applications, the module › template › handler tree, handler forms, Try it (inject with a
317
366
  short-lived token), Run only / lock checks
318
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)
319
370
  process-jobs.ts page designer: the Jobs tab of a background chain process
320
371
  template-spec.ts template component property form (Shared Components)
321
372
  websources.ts web credentials and REST data sources: property specs, secret status, Test, suggested columns,
@@ -338,13 +389,17 @@ src/
338
389
  ai-builder.ts App Builder AI (migration 062): the builder's AI service (meta.builder_ai), SQL Workshop → AI (SQL from a
339
390
  question, shown not run; explain), describe tables (meta.ai_table_note, COMMENT ON, AI drafts),
340
391
  create pages with AI (proposals checked by checkProposals, created with meta.generate_page)
341
- 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)
342
393
  ai.ts Workspace utilities → AI services (administrators: services, write-only encrypted keys, access and
343
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()
344
397
  supporting.ts supporting objects: review page, running the install/upgrade/deinstall scripts as the app's role in one transaction
345
398
  public/
346
399
  app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
347
- 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)
348
403
  code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies);
349
404
  (sprint 108) the current line, and read-only <pre data-code-view="sql|plpgsql|json|html"> highlighted;
350
405
  its colours are the --ce-* tokens in builder.css
@@ -389,6 +444,8 @@ test/
389
444
  worksheet.test.ts (sprint 91) the SQL Worksheet: statement at the cursor, Run Script log, plans, notices, history, navigator, Run as
390
445
  worksheet-txn.test.ts (sprint 107) transaction mode: the tab's transaction, Commit / Rollback, typed COMMIT, aborted, idle timeout, refusals,
391
446
  Explain in it, Change Log at COMMIT, the JSON endpoint, sign-out
447
+ datasets.test.ts (sprint 110) datasets: checks and SQL, proposed joins, the builder's pages, files, the workspace export;
448
+ a reporting application in two clicks, an editor publishes, a viewer opens the stable URL, pins
392
449
  quicksql.test.ts Quick SQL parser and DDL generator
393
450
  xml.test.ts XML reader: rows, attributes, paths, refused DTDs and entities, limits
394
451
  printing.test.ts report PDFs
@@ -403,6 +460,8 @@ test/
403
460
  rest-sources.test.ts REST data sources, web credentials, SSRF checks, invoke_api (mock service + HR page 23)
404
461
  rest-modules.test.ts (sprint 104) modules, templates, PL/pgSQL and media handlers, OpenAPI, the builder's tree and Try it
405
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)
406
465
  large-tables.test.ts row ranges, max_rows, row limits, lazy regions, region caching, streamed downloads (HR page 25)
407
466
  grid.test.ts interactive grid: aggregates, layouts per user, saved grid reports, master-detail, row actions (HR page 27)
408
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,29 @@
1
+ -- =====================================================================
2
+ -- HR example, part 63: a Data Reporter dataset (docs/guide/04-pages-and-
3
+ -- regions.md, "Datasets and reporting applications")
4
+ --
5
+ -- The dataset "hr" of the Default workspace: employees (hr.emp) joined to
6
+ -- their department (hr.dept, a left join on the foreign key deptno), the
7
+ -- columns business users may see with labels, format masks and
8
+ -- descriptions, and a row filter: active employees only. In the builder,
9
+ -- App Builder → Data Reporter → Create reporting application makes a
10
+ -- reporting application on it in two clicks; datasets are installation
11
+ -- data (a file of their own, and the workspace export), not part of the
12
+ -- HR application's export.
13
+ -- =====================================================================
14
+
15
+ insert into meta.dataset (workspace_id, name, label, description, schema_name, tables, columns, filters, created_by)
16
+ values (1, 'hr', 'HR', 'Active employees with their department.', 'hr',
17
+ '[{"alias": "t1", "table": "emp"},
18
+ {"alias": "t2", "table": "dept", "join": {"to": "t1", "type": "left", "on": [{"column": "deptno", "ref": "deptno"}]}}]',
19
+ '[{"name": "empno", "table": "t1", "column": "empno", "label": "No."},
20
+ {"name": "ename", "table": "t1", "column": "ename", "label": "Name"},
21
+ {"name": "job", "table": "t1", "column": "job", "label": "Job"},
22
+ {"name": "hiredate", "table": "t1", "column": "hiredate", "label": "Hire date"},
23
+ {"name": "sal", "table": "t1", "column": "sal", "label": "Salary", "format": "FML999G990D00", "description": "Monthly salary"},
24
+ {"name": "comm", "table": "t1", "column": "comm", "label": "Commission", "format": "FML999G990D00"},
25
+ {"name": "dname", "table": "t2", "column": "dname", "label": "Department"},
26
+ {"name": "loc", "table": "t2", "column": "loc", "label": "Location", "description": "Where the department is"}]',
27
+ '[{"table": "t1", "column": "active", "op": "eq", "value": "true"}]',
28
+ 'admin')
29
+ on conflict (workspace_id, name) do nothing;
@@ -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';