pgkiln 0.44.0 → 0.46.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 (91) hide show
  1. package/db/migrations/102_app_computations.sql +345 -0
  2. package/db/migrations/103_shortcuts.sql +341 -0
  3. package/db/migrations/104_report_queries.sql +413 -0
  4. package/db/migrations/105_component_settings_groups.sql +367 -0
  5. package/db/migrations/106_form_designer.sql +102 -0
  6. package/db/migrations/107_app_version.sql +9 -0
  7. package/docs/apex-feature-parity.md +15 -12
  8. package/docs/guide/02-concepts.md +24 -0
  9. package/docs/guide/03-builder.md +69 -13
  10. package/docs/guide/04-pages-and-regions.md +84 -3
  11. package/docs/guide/05-items.md +14 -2
  12. package/docs/guide/06-processing.md +22 -0
  13. package/docs/guide/09-reference.md +14 -1
  14. package/docs/guide/11-from-apex.md +2 -1
  15. package/docs/guide/12-development.md +8 -2
  16. package/docs/guide/16-files.md +23 -0
  17. package/docs/guide/18-cli.md +4 -0
  18. package/examples/hr/hr_61_form_designer.sql +80 -0
  19. package/package.json +1 -1
  20. package/public/app.css +79 -2
  21. package/public/app.js +307 -35
  22. package/public/builder.css +21 -1
  23. package/public/builder.js +67 -0
  24. package/public/sw.js +2 -1
  25. package/src/app.ts +9 -1
  26. package/src/appfiles.ts +18 -2
  27. package/src/builder/advisor.ts +7 -2
  28. package/src/builder/appwizard.ts +22 -6
  29. package/src/builder/arrange.ts +33 -12
  30. package/src/builder/code-editor.ts +7 -2
  31. package/src/builder/components.ts +86 -4
  32. package/src/builder/designer.ts +44 -17
  33. package/src/builder/errors.ts +65 -0
  34. package/src/builder/form-designer.ts +229 -0
  35. package/src/builder/forms.ts +1 -1
  36. package/src/builder/locks.ts +6 -0
  37. package/src/builder/newapp.ts +2 -1
  38. package/src/builder/object-browser.ts +81 -18
  39. package/src/builder/region-help.ts +2 -0
  40. package/src/builder/region-settings.ts +2 -0
  41. package/src/builder/reporter.ts +1 -1
  42. package/src/builder/routes.ts +7 -5
  43. package/src/builder/search.ts +3 -3
  44. package/src/builder/shared.ts +41 -8
  45. package/src/builder/subscriptions.ts +30 -4
  46. package/src/builder/ui.ts +2 -0
  47. package/src/cli/replace.ts +16 -2
  48. package/src/i18n/ar.ts +62 -0
  49. package/src/i18n/cs.ts +62 -0
  50. package/src/i18n/da.ts +62 -0
  51. package/src/i18n/de.ts +62 -0
  52. package/src/i18n/el.ts +62 -0
  53. package/src/i18n/es.ts +62 -0
  54. package/src/i18n/fi.ts +62 -0
  55. package/src/i18n/fr.ts +62 -0
  56. package/src/i18n/he.ts +62 -0
  57. package/src/i18n/it.ts +62 -0
  58. package/src/i18n/ja.ts +62 -0
  59. package/src/i18n/ko.ts +62 -0
  60. package/src/i18n/nb.ts +62 -0
  61. package/src/i18n/pl.ts +62 -0
  62. package/src/i18n/pt.ts +62 -0
  63. package/src/i18n/ru.ts +62 -0
  64. package/src/i18n/sv.ts +62 -0
  65. package/src/i18n/tr.ts +62 -0
  66. package/src/i18n/uk.ts +62 -0
  67. package/src/i18n/zh.ts +62 -0
  68. package/src/i18n.ts +124 -0
  69. package/src/metadata.ts +18 -3
  70. package/src/runtime/component-settings.ts +42 -0
  71. package/src/runtime/context.ts +4 -0
  72. package/src/runtime/devbar.ts +1 -1
  73. package/src/runtime/documents.ts +29 -3
  74. package/src/runtime/engine.ts +2 -2
  75. package/src/runtime/facet-state.ts +10 -5
  76. package/src/runtime/facets.ts +29 -5
  77. package/src/runtime/form-designer.ts +676 -0
  78. package/src/runtime/grid.ts +36 -11
  79. package/src/runtime/items.ts +47 -32
  80. package/src/runtime/lists.ts +1 -1
  81. package/src/runtime/logic.ts +12 -7
  82. package/src/runtime/pdf.ts +2 -1
  83. package/src/runtime/regions.ts +8 -3
  84. package/src/runtime/render.ts +8 -3
  85. package/src/runtime/report.ts +4 -3
  86. package/src/runtime/routes.ts +7 -3
  87. package/src/runtime/shortcuts.ts +43 -0
  88. package/src/runtime/smart-filters.ts +2 -1
  89. package/src/runtime/tasks.ts +6 -3
  90. package/src/security.ts +2 -1
  91. package/src/subscriptions.ts +67 -0
@@ -0,0 +1,367 @@
1
+ -- =====================================================================
2
+ -- 105: component settings and component groups (APEX: Shared Components →
3
+ -- Component Settings, Component Groups)
4
+ --
5
+ -- meta.app.component_settings: an application's defaults for its built-in
6
+ -- component types, used where a component sets nothing itself:
7
+ -- report_rows rows per page of a report (15)
8
+ -- grid_rows rows per page of an interactive grid (25)
9
+ -- popup_lov_rows rows per page of a popup list of values (25)
10
+ -- lov_max_rows rows a list of values reads (5,000)
11
+ -- rating_max stars of a star rating (5)
12
+ -- The builder bounds the values (src/runtime/component-settings.ts); the
13
+ -- runtime bounds them again. Exported with the application (export_app
14
+ -- copies every column of meta.app).
15
+ --
16
+ -- meta.component_group: a named set of an application's components that
17
+ -- other applications subscribe to at once (subscriptions, migration 056):
18
+ -- members are "kind:name" of its lists of values, authorization schemes,
19
+ -- build options, template components and lists. Builder data: the
20
+ -- runtime does not read it.
21
+ -- export_app / import_app are redefined from 104 to carry component_groups.
22
+ -- =====================================================================
23
+ alter table meta.app add column component_settings jsonb not null default '{}'
24
+ check (jsonb_typeof(component_settings) = 'object');
25
+ comment on column meta.app.component_settings is 'Component settings (105): defaults of built-in component types (report_rows, grid_rows, popup_lov_rows, lov_max_rows, rating_max)';
26
+
27
+ create table meta.component_group (
28
+ id serial primary key,
29
+ app_id int not null references meta.app on delete cascade,
30
+ name text not null check (name ~ '^[A-Za-z0-9][A-Za-z0-9 _.-]{0,59}$'),
31
+ description text,
32
+ members text[] not null default '{}' check (cardinality(members) <= 500),
33
+ unique (app_id, name)
34
+ );
35
+ revoke all on meta.component_group from public;
36
+ comment on table meta.component_group is 'Component groups (105): sets of an application''s components ("kind:name") that other applications subscribe to at once';
37
+
38
+ -- (090) like the other tables of an application
39
+ alter table meta.component_group enable row level security;
40
+ create policy workspace on meta.component_group
41
+ using ((select meta.isolation_off()) or app_id in (select meta.visible_apps()))
42
+ with check ((select meta.isolation_off()) or app_id in (select meta.visible_apps()));
43
+
44
+ create or replace function meta.export_app(p_alias text) returns jsonb
45
+ language sql stable set search_path = meta, pg_catalog as $$
46
+ select jsonb_build_object(
47
+ 'format', 'pgkiln/2',
48
+ -- (083) availability and build status belong to the installation
49
+ 'app', to_jsonb(a) - 'id' - 'created_at' - 'updated_at' - 'availability' - 'availability_message' - 'availability_users' - 'build_status',
50
+ 'authz_schemes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.authz_scheme x where x.app_id = a.id), '[]'),
51
+ 'app_items', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_item x where x.app_id = a.id), '[]'),
52
+ 'app_processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.seq, x.id) from meta.app_process x where x.app_id = a.id), '[]'),
53
+ 'lovs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.lov x where x.app_id = a.id), '[]'),
54
+ 'group_roles', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.group_name, x.role) from meta.app_group_role x where x.app_id = a.id), '[]'),
55
+ 'text_messages', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.name, x.language) from meta.text_message x where x.app_id = a.id), '[]'),
56
+ 'translations', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.language, x.source) from meta.translation x where x.app_id = a.id), '[]'),
57
+ 'report_layouts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'logo' || jsonb_build_object('logo', encode(x.logo, 'base64')) order by x.name)
58
+ from meta.report_layout x where x.app_id = a.id), '[]'),
59
+ 'automations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'next_run_at' - 'last_run_at' - 'last_status' - 'code' order by x.name)
60
+ from meta.automation x where x.app_id = a.id), '[]'),
61
+ -- (044) the actions of the automations, by automation name
62
+ 'automation_actions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.automation_name collate "C", x.seq, x.name collate "C")
63
+ from meta.automation_action x where x.app_id = a.id), '[]'),
64
+ 'document_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.document_template x where x.app_id = a.id), '[]'),
65
+ 'task_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.task_definition x where x.app_id = a.id), '[]'),
66
+ 'workflow_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.workflow_definition x where x.app_id = a.id), '[]'),
67
+ 'rest_modules', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.rest_module x where x.app_id = a.id), '[]'),
68
+ 'template_components', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.static_id) from meta.template_component x where x.app_id = a.id), '[]'),
69
+ 'build_options', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.build_option x where x.app_id = a.id), '[]'),
70
+ -- (030) secrets never leave the installation: they are entered again after an import
71
+ -- (050) nor do the password and refresh token of the OAuth2 flows, or the token state
72
+ 'web_credentials', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at' order by x.name)
73
+ from meta.web_credential x where x.app_id = a.id), '[]'),
74
+ -- (050) the synchronisation's state (next and last run) belongs to the installation
75
+ 'rest_sources', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'sync_next_at' - 'sync_last_at' - 'sync_last_status' order by x.name)
76
+ from meta.rest_source x where x.app_id = a.id), '[]'),
77
+ -- (041) data load definitions
78
+ 'data_load_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.data_load_def x where x.app_id = a.id), '[]'),
79
+ -- (042) lists; entries keep their ids so parents can be remapped on import
80
+ 'lists', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.list x where x.app_id = a.id), '[]'),
81
+ 'list_entries', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.list_name, x.parent_id nulls first, x.seq, x.id) from meta.list_entry x where x.app_id = a.id), '[]'),
82
+ -- (042) supporting objects: install, upgrade and deinstall scripts (never run on import)
83
+ 'supporting_scripts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.supporting_script x where x.app_id = a.id), '[]'),
84
+ -- (069) plug-ins (their files are static files, their templates template components)
85
+ 'plugins', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.plugin x where x.app_id = a.id), '[]'),
86
+ -- (080) search configurations
87
+ 'search_configs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.search_config x where x.app_id = a.id), '[]'),
88
+ -- (081) e-mail templates
89
+ 'email_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.email_template x where x.app_id = a.id), '[]'),
90
+ -- (086) application settings (with their values) and dynamic translations
91
+ 'app_settings', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_setting x where x.app_id = a.id), '[]'),
92
+ 'page_groups', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.page_group x where x.app_id = a.id), '[]'),
93
+ -- (105) component groups
94
+ 'component_groups', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.component_group x where x.app_id = a.id), '[]'),
95
+ -- (104) report queries
96
+ 'report_queries', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.report_query x where x.app_id = a.id), '[]'),
97
+ -- (103) shortcuts
98
+ 'shortcuts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.shortcut x where x.app_id = a.id), '[]'),
99
+ -- (102) application computations
100
+ 'app_computations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.seq, x.id) from meta.app_computation x where x.app_id = a.id), '[]'),
101
+ 'dynamic_translations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.language, x.source collate "C") from meta.dynamic_translation x where x.app_id = a.id), '[]'),
102
+ -- (068) static application files (JavaScript, CSS, images, fonts) as base64
103
+ 'static_files', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'content' - 'updated_at' || jsonb_build_object('content', translate(encode(x.content, 'base64'), E'\n', '')) order by x.name)
104
+ from meta.static_file x where x.app_id = a.id), '[]'),
105
+ -- nav entries and regions keep their ids, so parents and references can be remapped on import
106
+ 'nav', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.parent_id nulls first, x.seq, x.id) from meta.nav_entry x where x.app_id = a.id), '[]'),
107
+ 'pages', coalesce((
108
+ select jsonb_agg(to_jsonb(p) - 'id' - 'app_id' || jsonb_build_object(
109
+ 'regions', coalesce((select jsonb_agg(to_jsonb(r) - 'page_id' order by r.seq, r.id) from meta.region r where r.page_id = p.id), '[]'),
110
+ 'items', coalesce((select jsonb_agg(to_jsonb(i) - 'id' - 'page_id' order by i.seq, i.id) from meta.item i where i.page_id = p.id), '[]'),
111
+ 'buttons', coalesce((select jsonb_agg(to_jsonb(b) - 'id' - 'page_id' order by b.seq, b.id) from meta.button b where b.page_id = p.id), '[]'),
112
+ 'dynamic_actions', coalesce((select jsonb_agg(to_jsonb(d) - 'id' - 'page_id' order by d.seq, d.id) from meta.dynamic_action d where d.page_id = p.id), '[]'),
113
+ 'validations', coalesce((select jsonb_agg(to_jsonb(v) - 'id' - 'page_id' order by v.seq, v.id) from meta.validation v where v.page_id = p.id), '[]'),
114
+ 'processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.process x where x.page_id = p.id), '[]'),
115
+ 'computations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.computation x where x.page_id = p.id), '[]'),
116
+ 'branches', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.branch x where x.page_id = p.id), '[]')
117
+ ) order by p.page_no)
118
+ from meta.page p where p.app_id = a.id), '[]'))
119
+ from meta.app a
120
+ where a.alias = p_alias
121
+ $$;
122
+
123
+ -- ---------------------------------------------------------------- import
124
+ -- Same as 081, plus app_settings and dynamic_translations.
125
+
126
+ create or replace function meta.import_app(p_doc jsonb, p_alias text default null) returns int
127
+ language plpgsql set search_path = meta, pg_catalog as $$
128
+ declare
129
+ v_app_id int;
130
+ v_page_id int;
131
+ v_page jsonb;
132
+ v_e jsonb;
133
+ v_rmap jsonb;
134
+ v_nmap jsonb := '{}';
135
+ v_lmap jsonb := '{}';
136
+ v_new_id int;
137
+ begin
138
+ if p_doc->>'format' is distinct from 'pgkiln/2' then
139
+ raise exception 'unsupported export format %', coalesce(p_doc->>'format', '(none)');
140
+ end if;
141
+
142
+ insert into meta.app
143
+ select (jsonb_populate_record(null::meta.app, p_doc->'app' || jsonb_build_object(
144
+ 'id', nextval('meta.app_id_seq'),
145
+ 'alias', coalesce(p_alias, p_doc->'app'->>'alias'),
146
+ 'created_at', now(), 'updated_at', now()))).*
147
+ returning id into v_app_id;
148
+
149
+ insert into meta.authz_scheme
150
+ select (jsonb_populate_record(null::meta.authz_scheme, e || jsonb_build_object('id', nextval('meta.authz_scheme_id_seq'), 'app_id', v_app_id))).*
151
+ from jsonb_array_elements(coalesce(p_doc->'authz_schemes', '[]')) e;
152
+ insert into meta.app_item
153
+ select (jsonb_populate_record(null::meta.app_item, e || jsonb_build_object('id', nextval('meta.app_item_id_seq'), 'app_id', v_app_id))).*
154
+ from jsonb_array_elements(coalesce(p_doc->'app_items', '[]')) e;
155
+ insert into meta.app_process
156
+ select (jsonb_populate_record(null::meta.app_process, e || jsonb_build_object('id', nextval('meta.app_process_id_seq'), 'app_id', v_app_id))).*
157
+ from jsonb_array_elements(coalesce(p_doc->'app_processes', '[]')) e;
158
+ insert into meta.lov
159
+ select (jsonb_populate_record(null::meta.lov, e || jsonb_build_object('id', nextval('meta.lov_id_seq'), 'app_id', v_app_id))).*
160
+ from jsonb_array_elements(coalesce(p_doc->'lovs', '[]')) e;
161
+ insert into meta.app_group_role
162
+ select (jsonb_populate_record(null::meta.app_group_role, e || jsonb_build_object('app_id', v_app_id))).*
163
+ from jsonb_array_elements(coalesce(p_doc->'group_roles', '[]')) e;
164
+ insert into meta.text_message
165
+ select (jsonb_populate_record(null::meta.text_message, e || jsonb_build_object('app_id', v_app_id))).*
166
+ from jsonb_array_elements(coalesce(p_doc->'text_messages', '[]')) e;
167
+ insert into meta.translation
168
+ select (jsonb_populate_record(null::meta.translation, e || jsonb_build_object('app_id', v_app_id))).*
169
+ from jsonb_array_elements(coalesce(p_doc->'translations', '[]')) e;
170
+ insert into meta.report_layout
171
+ select (jsonb_populate_record(null::meta.report_layout, (e - 'logo') || jsonb_build_object(
172
+ 'id', nextval('meta.report_layout_id_seq'), 'app_id', v_app_id,
173
+ 'logo', null))).*
174
+ from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e;
175
+ update meta.report_layout l
176
+ set logo = decode(e->>'logo', 'base64')
177
+ from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e
178
+ where l.app_id = v_app_id and l.name = e->>'name' and e->>'logo' is not null;
179
+ -- imported automations start switched off: the copy must not run the original's jobs unasked.
180
+ -- (044) a file with actions: the automations' code is ignored; an older file: code becomes the single action
181
+ insert into meta.automation
182
+ select (jsonb_populate_record(null::meta.automation, '{"error_handling": "stop"}'::jsonb
183
+ || jsonb_strip_nulls(case when p_doc ? 'automation_actions' then e - 'code' else e end)
184
+ || jsonb_build_object('id', nextval('meta.automation_id_seq'), 'app_id', v_app_id, 'enabled', false))).*
185
+ from jsonb_array_elements(coalesce(p_doc->'automations', '[]')) e;
186
+ insert into meta.automation_action
187
+ select (jsonb_populate_record(null::meta.automation_action, '{"seq": 10}'::jsonb || jsonb_strip_nulls(e)
188
+ || jsonb_build_object('id', nextval('meta.automation_action_id_seq'), 'app_id', v_app_id))).*
189
+ from jsonb_array_elements(coalesce(p_doc->'automation_actions', '[]')) e;
190
+ insert into meta.document_template
191
+ select (jsonb_populate_record(null::meta.document_template, e || jsonb_build_object('id', nextval('meta.document_template_id_seq'), 'app_id', v_app_id))).*
192
+ from jsonb_array_elements(coalesce(p_doc->'document_templates', '[]')) e;
193
+ insert into meta.task_definition
194
+ select (jsonb_populate_record(null::meta.task_definition, e || jsonb_build_object('id', nextval('meta.task_definition_id_seq'), 'app_id', v_app_id))).*
195
+ from jsonb_array_elements(coalesce(p_doc->'task_definitions', '[]')) e;
196
+ insert into meta.workflow_definition
197
+ select (jsonb_populate_record(null::meta.workflow_definition, e || jsonb_build_object('id', nextval('meta.workflow_definition_id_seq'), 'app_id', v_app_id))).*
198
+ from jsonb_array_elements(coalesce(p_doc->'workflow_definitions', '[]')) e;
199
+ insert into meta.rest_module
200
+ select (jsonb_populate_record(null::meta.rest_module, e || jsonb_build_object('id', nextval('meta.rest_module_id_seq'), 'app_id', v_app_id))).*
201
+ from jsonb_array_elements(coalesce(p_doc->'rest_modules', '[]')) e;
202
+ -- (028) template components: regions and report columns refer to them by static id
203
+ insert into meta.template_component
204
+ select (jsonb_populate_record(null::meta.template_component, e || jsonb_build_object('id', nextval('meta.template_component_id_seq'), 'app_id', v_app_id))).*
205
+ from jsonb_array_elements(coalesce(p_doc->'template_components', '[]')) e;
206
+ -- (029) build options (components name them by name)
207
+ insert into meta.build_option
208
+ select (jsonb_populate_record(null::meta.build_option, e || jsonb_build_object('id', nextval('meta.build_option_id_seq'), 'app_id', v_app_id))).*
209
+ from jsonb_array_elements(coalesce(p_doc->'build_options', '[]')) e;
210
+ -- (030) web credentials arrive without a secret, whatever the document holds
211
+ -- (050) nor a password or refresh token
212
+ insert into meta.web_credential
213
+ select (jsonb_populate_record(null::meta.web_credential, '{"type": "basic", "valid_for": [], "grant_type": "client_credentials"}'::jsonb
214
+ || jsonb_strip_nulls(e - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at')
215
+ || jsonb_build_object('id', nextval('meta.web_credential_id_seq'), 'app_id', v_app_id))).*
216
+ from jsonb_array_elements(coalesce(p_doc->'web_credentials', '[]')) e;
217
+ -- (050) write-back operations and synchronisation; an imported synchronisation starts switched off
218
+ insert into meta.rest_source
219
+ select (jsonb_populate_record(null::meta.rest_source, '{"method": "GET", "headers": {}, "params": [], "columns": [], "cache_seconds": 0, "timeout_s": 10, "max_rows": 1000,
220
+ "operations": {}, "key_columns": [], "sync_mode": "merge", "sync_delete": false, "sync_time_zone": "UTC"}'::jsonb
221
+ || jsonb_strip_nulls(e - 'sync_next_at' - 'sync_last_at' - 'sync_last_status')
222
+ || jsonb_build_object('id', nextval('meta.rest_source_id_seq'), 'app_id', v_app_id, 'sync_enabled', false))).*
223
+ from jsonb_array_elements(coalesce(p_doc->'rest_sources', '[]')) e;
224
+
225
+ -- (041) data load definitions (the data_load process names them)
226
+ insert into meta.data_load_def
227
+ select (jsonb_populate_record(null::meta.data_load_def, '{"format": "auto", "headers": true, "mode": "append", "skip_errors": false, "columns": []}'::jsonb
228
+ || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.data_load_def_id_seq'), 'app_id', v_app_id))).*
229
+ from jsonb_array_elements(coalesce(p_doc->'data_load_definitions', '[]')) e;
230
+
231
+ -- (042) lists and their entries, supporting scripts. Entries are inserted
232
+ -- without a parent first and the parents set afterwards, so the order of
233
+ -- the entries in the document doesn't matter (a moved entry may have a
234
+ -- higher id than its children).
235
+ insert into meta.list
236
+ select (jsonb_populate_record(null::meta.list, '{"type": "static"}'::jsonb || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.list_id_seq'), 'app_id', v_app_id))).*
237
+ from jsonb_array_elements(coalesce(p_doc->'lists', '[]')) e;
238
+ for v_e in select * from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) loop
239
+ insert into meta.list_entry
240
+ select (jsonb_populate_record(null::meta.list_entry, '{"target_items": {}}'::jsonb || jsonb_strip_nulls(v_e) || jsonb_build_object(
241
+ 'id', nextval('meta.list_entry_id_seq'), 'app_id', v_app_id, 'parent_id', null))).*
242
+ returning id into v_new_id;
243
+ if v_e->>'id' is not null then
244
+ v_lmap := v_lmap || jsonb_build_object(v_e->>'id', v_new_id);
245
+ end if;
246
+ end loop;
247
+ update meta.list_entry x
248
+ set parent_id = (v_lmap->>(e->>'parent_id'))::int
249
+ from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) e
250
+ where e->>'parent_id' is not null and v_lmap ? (e->>'parent_id') and v_lmap ? (e->>'id')
251
+ and x.id = (v_lmap->>(e->>'id'))::int;
252
+ insert into meta.supporting_script
253
+ select (jsonb_populate_record(null::meta.supporting_script, '{"kind": "install", "seq": 10, "script": ""}'::jsonb || jsonb_strip_nulls(e)
254
+ || jsonb_build_object('id', nextval('meta.supporting_script_id_seq'), 'app_id', v_app_id))).*
255
+ from jsonb_array_elements(coalesce(p_doc->'supporting_scripts', '[]')) e;
256
+
257
+ -- (069) plug-ins
258
+ insert into meta.plugin
259
+ select (jsonb_populate_record(null::meta.plugin, e || jsonb_build_object('id', nextval('meta.plugin_id_seq'), 'app_id', v_app_id))).*
260
+ from jsonb_array_elements(coalesce(p_doc->'plugins', '[]')) e;
261
+
262
+ -- (080) search configurations (search regions name them)
263
+ insert into meta.search_config
264
+ select (jsonb_populate_record(null::meta.search_config, '{"seq": 10, "source_type": "table", "searchable_columns": [], "max_results": 10}'::jsonb
265
+ || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.search_config_id_seq'), 'app_id', v_app_id))).*
266
+ from jsonb_array_elements(coalesce(p_doc->'search_configs', '[]')) e;
267
+
268
+ -- (081) e-mail templates (meta.send_mail_template and the send_email process name them)
269
+ insert into meta.email_template
270
+ select (jsonb_populate_record(null::meta.email_template, e || jsonb_build_object('id', nextval('meta.email_template_id_seq'), 'app_id', v_app_id))).*
271
+ from jsonb_array_elements(coalesce(p_doc->'email_templates', '[]')) e;
272
+
273
+ -- (105) component groups
274
+ insert into meta.component_group
275
+ select (jsonb_populate_record(null::meta.component_group, '{"members": []}'::jsonb || jsonb_strip_nulls(e)
276
+ || jsonb_build_object('id', nextval('meta.component_group_id_seq'), 'app_id', v_app_id))).*
277
+ from jsonb_array_elements(coalesce(p_doc->'component_groups', '[]')) e;
278
+
279
+ -- (104) report queries
280
+ insert into meta.report_query
281
+ select (jsonb_populate_record(null::meta.report_query, '{"format": "pdf"}'::jsonb || jsonb_strip_nulls(e)
282
+ || jsonb_build_object('id', nextval('meta.report_query_id_seq'), 'app_id', v_app_id))).*
283
+ from jsonb_array_elements(coalesce(p_doc->'report_queries', '[]')) e;
284
+
285
+ -- (103) shortcuts
286
+ insert into meta.shortcut
287
+ select (jsonb_populate_record(null::meta.shortcut, '{"type": "text"}'::jsonb || jsonb_strip_nulls(e)
288
+ || jsonb_build_object('id', nextval('meta.shortcut_id_seq'), 'app_id', v_app_id))).*
289
+ from jsonb_array_elements(coalesce(p_doc->'shortcuts', '[]')) e;
290
+
291
+ -- (102) application computations
292
+ insert into meta.app_computation
293
+ select (jsonb_populate_record(null::meta.app_computation, '{"seq": 10, "point": "before_header", "type": "static"}'::jsonb || jsonb_strip_nulls(e)
294
+ || jsonb_build_object('id', nextval('meta.app_computation_id_seq'), 'app_id', v_app_id))).*
295
+ from jsonb_array_elements(coalesce(p_doc->'app_computations', '[]')) e;
296
+
297
+ -- (089) page groups
298
+ insert into meta.page_group
299
+ select (jsonb_populate_record(null::meta.page_group, e || jsonb_build_object('id', nextval('meta.page_group_id_seq'), 'app_id', v_app_id))).*
300
+ from jsonb_array_elements(coalesce(p_doc->'page_groups', '[]')) e;
301
+
302
+ -- (086) application settings and dynamic translations
303
+ insert into meta.app_setting
304
+ select (jsonb_populate_record(null::meta.app_setting, e || jsonb_build_object('id', nextval('meta.app_setting_id_seq'), 'app_id', v_app_id))).*
305
+ from jsonb_array_elements(coalesce(p_doc->'app_settings', '[]')) e;
306
+ insert into meta.dynamic_translation
307
+ select (jsonb_populate_record(null::meta.dynamic_translation, e || jsonb_build_object('id', nextval('meta.dynamic_translation_id_seq'), 'app_id', v_app_id))).*
308
+ from jsonb_array_elements(coalesce(p_doc->'dynamic_translations', '[]')) e;
309
+
310
+ -- (068) static application files
311
+ insert into meta.static_file (app_id, name, mime, content, updated_at)
312
+ select v_app_id, e->>'name', e->>'mime', decode(coalesce(e->>'content', ''), 'base64'), now()
313
+ from jsonb_array_elements(coalesce(p_doc->'static_files', '[]')) e;
314
+
315
+ for v_e in select * from jsonb_array_elements(coalesce(p_doc->'nav', '[]')) loop
316
+ insert into meta.nav_entry
317
+ select (jsonb_populate_record(null::meta.nav_entry, v_e || jsonb_build_object(
318
+ 'id', nextval('meta.nav_entry_id_seq'), 'app_id', v_app_id,
319
+ 'parent_id', v_nmap->>(v_e->>'parent_id')))).*
320
+ returning id into v_new_id;
321
+ v_nmap := v_nmap || jsonb_build_object(v_e->>'id', v_new_id);
322
+ end loop;
323
+
324
+ for v_page in select * from jsonb_array_elements(coalesce(p_doc->'pages', '[]')) loop
325
+ insert into meta.page
326
+ select (jsonb_populate_record(null::meta.page, v_page || jsonb_build_object('id', nextval('meta.page_id_seq'), 'app_id', v_app_id))).*
327
+ returning id into v_page_id;
328
+
329
+ v_rmap := '{}';
330
+ for v_e in select * from jsonb_array_elements(coalesce(v_page->'regions', '[]')) loop
331
+ insert into meta.region
332
+ select (jsonb_populate_record(null::meta.region, v_e || jsonb_build_object('id', nextval('meta.region_id_seq'), 'page_id', v_page_id))).*
333
+ returning id into v_new_id;
334
+ v_rmap := v_rmap || jsonb_build_object(v_e->>'id', v_new_id);
335
+ end loop;
336
+ -- facet and map regions point at their report region by id
337
+ update meta.region r
338
+ set config = jsonb_set(r.config, '{report}', to_jsonb((v_rmap->>(r.config->>'report'))::int))
339
+ where r.page_id = v_page_id and r.type in ('facets', 'smart_filters', 'map') and v_rmap ? (r.config->>'report');
340
+
341
+ insert into meta.item
342
+ select (jsonb_populate_record(null::meta.item, e || jsonb_build_object('id', nextval('meta.item_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
343
+ from jsonb_array_elements(coalesce(v_page->'items', '[]')) e;
344
+ insert into meta.button
345
+ select (jsonb_populate_record(null::meta.button, e || jsonb_build_object('id', nextval('meta.button_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
346
+ from jsonb_array_elements(coalesce(v_page->'buttons', '[]')) e;
347
+ insert into meta.dynamic_action
348
+ select (jsonb_populate_record(null::meta.dynamic_action, e || jsonb_build_object('id', nextval('meta.dynamic_action_id_seq'), 'page_id', v_page_id, 'affected_region_id', v_rmap->>(e->>'affected_region_id')))).*
349
+ from jsonb_array_elements(coalesce(v_page->'dynamic_actions', '[]')) e;
350
+ insert into meta.validation
351
+ select (jsonb_populate_record(null::meta.validation, e || jsonb_build_object('id', nextval('meta.validation_id_seq'), 'page_id', v_page_id))).*
352
+ from jsonb_array_elements(coalesce(v_page->'validations', '[]')) e;
353
+ insert into meta.process
354
+ select (jsonb_populate_record(null::meta.process, e || jsonb_build_object('id', nextval('meta.process_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
355
+ from jsonb_array_elements(coalesce(v_page->'processes', '[]')) e;
356
+ insert into meta.computation
357
+ select (jsonb_populate_record(null::meta.computation, e || jsonb_build_object('id', nextval('meta.computation_id_seq'), 'page_id', v_page_id))).*
358
+ from jsonb_array_elements(coalesce(v_page->'computations', '[]')) e;
359
+ insert into meta.branch
360
+ select (jsonb_populate_record(null::meta.branch, e || jsonb_build_object('id', nextval('meta.branch_id_seq'), 'page_id', v_page_id))).*
361
+ from jsonb_array_elements(coalesce(v_page->'branches', '[]')) e;
362
+ end loop;
363
+
364
+ return v_app_id;
365
+ end
366
+ $$;
367
+
@@ -0,0 +1,102 @@
1
+ -- ---------------------------------------------------------------------
2
+ -- 106: Form Designer: the application's administrators build their own
3
+ -- data entry forms in the running application.
4
+ --
5
+ -- The developer places a region of type 'form_designer' and lists in its
6
+ -- settings (config.sources) the tables and views it offers: the columns
7
+ -- forms may show and change, the column that identifies a row, and what
8
+ -- forms on it may do at most (add, change, delete rows). Who may design
9
+ -- forms is an authorization scheme (config.design_authz); without one
10
+ -- nobody can. Those settings are part of the region, so they travel with
11
+ -- the application export.
12
+ --
13
+ -- A user form is a definition (source, fields with labels, help, required,
14
+ -- display and default, the columns of its row list, the actions it allows)
15
+ -- under a name, kept per region; published forms are used by everyone who
16
+ -- can see the region, drafts only by designers. User forms are data of the
17
+ -- installation (like meta.data_report): not part of the application export,
18
+ -- kept when an application is replaced. Applications reach the table only
19
+ -- through meta.user_forms and the two functions; the runtime checks every
20
+ -- definition against the region's offered columns before it renders or
21
+ -- saves, and reads and writes rows as the application's database role, so
22
+ -- grants and row level security apply.
23
+ -- ---------------------------------------------------------------------
24
+ alter table meta.region drop constraint region_type_check;
25
+ alter table meta.region add constraint region_type_check
26
+ check (type in ('report', 'form', 'chart', 'cards', 'static', 'grid', 'calendar', 'dynamic', 'facets', 'tasks', 'workflows', 'map', 'tree',
27
+ 'template_component', 'smart_filters', 'display_selector', 'list', 'data_reporter', 'ai_assistant', 'plugin', 'url', 'search',
28
+ 'login', 'form_designer'));
29
+
30
+ create table meta.user_form (
31
+ id serial primary key,
32
+ app_id int not null references meta.app on delete cascade,
33
+ region_id int not null references meta.region on delete cascade,
34
+ name text not null check (length(btrim(name)) between 1 and 80),
35
+ description text check (length(description) <= 500),
36
+ published boolean not null default false,
37
+ definition jsonb not null check (jsonb_typeof(definition) = 'object' and length(definition::text) <= 32000),
38
+ created_by text not null,
39
+ updated_by text not null,
40
+ created_at timestamptz not null default now(),
41
+ updated_at timestamptz not null default now(),
42
+ unique (region_id, name)
43
+ );
44
+ create index user_form_app_idx on meta.user_form (app_id);
45
+ revoke all on meta.user_form from public;
46
+
47
+ create view meta.user_forms with (security_barrier) as
48
+ select id, region_id, name, description, published, definition, created_by, updated_by, created_at, updated_at
49
+ from meta.user_form
50
+ where app_id = meta.app_id();
51
+ grant select on meta.user_forms to public;
52
+
53
+ -- Save a form of a Form Designer region of the current application: a new
54
+ -- one (p_id null) or an existing one (p_id). Returns its id. Whether the
55
+ -- user may design forms (the region's authorization scheme) is checked by
56
+ -- the runtime before it calls this.
57
+ create function meta.save_user_form(p_region int, p_id int, p_name text, p_description text, p_definition jsonb, p_published boolean default false)
58
+ returns int
59
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
60
+ declare
61
+ v_user text := meta.app_user();
62
+ v_app int := meta.app_id();
63
+ v_id int;
64
+ begin
65
+ if v_user is null or v_user = 'nobody' then
66
+ raise exception 'sign in to design forms';
67
+ end if;
68
+ if not exists (select 1 from region r join page p on p.id = r.page_id where r.id = p_region and p.app_id = v_app and r.type = 'form_designer') then
69
+ raise exception 'unknown form designer region %', p_region;
70
+ end if;
71
+ begin
72
+ if p_id is null then
73
+ insert into user_form (app_id, region_id, name, description, published, definition, created_by, updated_by)
74
+ values (v_app, p_region, btrim(p_name), nullif(btrim(p_description), ''), coalesce(p_published, false), p_definition, v_user, v_user)
75
+ returning id into v_id;
76
+ else
77
+ update user_form
78
+ set name = btrim(p_name), description = nullif(btrim(p_description), ''), published = coalesce(p_published, false),
79
+ definition = p_definition, updated_by = v_user, updated_at = now()
80
+ where id = p_id and app_id = v_app and region_id = p_region
81
+ returning id into v_id;
82
+ if v_id is null then
83
+ raise exception 'form % does not exist', p_id;
84
+ end if;
85
+ end if;
86
+ exception when unique_violation then
87
+ raise exception 'There is already a form named "%".', btrim(p_name) using errcode = 'P0001';
88
+ end;
89
+ return v_id;
90
+ end
91
+ $$;
92
+
93
+ -- Delete a form of a Form Designer region of the current application.
94
+ create function meta.delete_user_form(p_region int, p_id int) returns boolean
95
+ language sql security definer set search_path = meta, pg_catalog as $$
96
+ with d as (delete from user_form where id = p_id and region_id = p_region and app_id = meta.app_id()
97
+ and meta.app_user() is not null and meta.app_user() <> 'nobody' returning 1)
98
+ select exists (select 1 from d)
99
+ $$;
100
+
101
+ revoke all on function meta.save_user_form(int, int, text, text, jsonb, boolean), meta.delete_user_form(int, int) from public;
102
+ grant execute on function meta.save_user_form(int, int, text, text, jsonb, boolean), meta.delete_user_form(int, int) to public;
@@ -0,0 +1,9 @@
1
+ -- ---------------------------------------------------------------------
2
+ -- 107: an application's version (APEX: Application Definition → Version,
3
+ -- "Release 1.0"), shown in the footer of its pages with "Back to top".
4
+ -- New applications start with 'Release 1.0'; existing ones have none (no
5
+ -- version in the footer) until a developer sets one in Settings. It travels
6
+ -- with the export like the other columns of meta.app.
7
+ -- ---------------------------------------------------------------------
8
+ alter table meta.app add column version text check (length(version) <= 40);
9
+ comment on column meta.app.version is 'The application''s version, e.g. Release 1.0: shown in its pages'' footer (107)';