pgkiln 0.47.0 → 0.49.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.
- package/SECURITY.md +2 -2
- package/db/migrations/109_builder_pins.sql +19 -0
- package/db/migrations/110_sql_charts.sql +23 -0
- package/db/migrations/111_sql_diagram.sql +24 -0
- package/db/migrations/113_rest_objects.sql +49 -0
- package/db/migrations/116_dump_jobs.sql +41 -0
- package/db/migrations/117_rest_handlers.sql +63 -0
- package/db/migrations/118_change_log.sql +54 -0
- package/docs/apex-feature-parity.md +11 -11
- package/docs/guide/01-installation.md +4 -0
- package/docs/guide/03-builder.md +267 -7
- package/docs/guide/08-security.md +29 -0
- package/docs/guide/12-development.md +70 -6
- package/docs/guide/13-rest-api.md +133 -10
- package/docs/guide/15-extensions.md +1 -1
- package/examples/hr/hr_62_rest_module.sql +39 -0
- package/package.json +1 -1
- package/public/app.css +1 -1
- package/public/builder-icons.svg +2 -0
- package/public/builder.css +442 -173
- package/public/builder.js +48 -0
- package/public/data-modeler.css +56 -0
- package/public/data-modeler.js +367 -0
- package/public/palette.js +133 -0
- package/public/sql-monitor.js +73 -0
- package/public/worksheet.css +49 -0
- package/public/worksheet.js +4 -0
- package/src/app.ts +10 -0
- package/src/automations.ts +6 -0
- package/src/binds.ts +2 -1
- package/src/builder/advisor.ts +24 -2
- package/src/builder/autorest.ts +363 -0
- package/src/builder/changelog.ts +240 -0
- package/src/builder/components.ts +1 -1
- package/src/builder/data-modeler.ts +382 -0
- package/src/builder/datapump.ts +364 -0
- package/src/builder/db-users.ts +472 -0
- package/src/builder/ddl-tools.ts +13 -4
- package/src/builder/find-workspace.ts +2 -2
- package/src/builder/launchpad.ts +140 -0
- package/src/builder/object-actions.ts +1 -0
- package/src/builder/object-browser.ts +7 -1
- package/src/builder/plan-view.ts +25 -0
- package/src/builder/rest-modules.ts +629 -0
- package/src/builder/rest.ts +1 -0
- package/src/builder/routes.ts +4 -2
- package/src/builder/scheduling.ts +495 -0
- package/src/builder/sql-charts.ts +203 -0
- package/src/builder/sql-monitor.ts +397 -0
- package/src/builder/sql-tools.ts +53 -0
- package/src/builder/sql-utilities.ts +7 -39
- package/src/builder/sql.ts +20 -9
- package/src/builder/table-editor.ts +4 -1
- package/src/builder/top-sql.ts +24 -6
- package/src/builder/ui.ts +25 -17
- package/src/builder/worksheet.ts +35 -28
- package/src/builder/workspaces.ts +4 -2
- package/src/changelog.ts +109 -0
- package/src/cli/replace.ts +3 -1
- package/src/cron-schedule.ts +282 -0
- package/src/datamodel.ts +324 -0
- package/src/datapump.ts +546 -0
- package/src/dbroles.ts +215 -0
- package/src/dumpscript.ts +407 -0
- package/src/pgcron.ts +225 -0
- package/src/runtime/autorest.ts +336 -0
- package/src/runtime/rest-handlers.ts +75 -0
- package/src/runtime/rest.ts +120 -26
- package/src/session.ts +2 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# 13. REST APIs
|
|
2
2
|
|
|
3
|
-
pgkiln offers
|
|
3
|
+
pgkiln offers three ways to expose data securely over REST (mobile apps, integrations, scripts),
|
|
4
4
|
which can be combined:
|
|
5
5
|
|
|
6
|
-
| | [REST modules](#rest-modules-in-the-builder) | [PostgREST](#how-it-works) |
|
|
7
|
-
|
|
8
|
-
| What | Endpoints you define in the builder: a method, a path and the SQL | A separate service that turns a schema of views and functions into an API |
|
|
9
|
-
| Served by | pgkiln itself, under `/a/<alias>/rest/<module>/` | PostgREST, next to pgkiln |
|
|
10
|
-
| Runs as | the application's database role (like its pages) | a dedicated API role |
|
|
11
|
-
| Good for | a handful of tailored endpoints, field apps, integrations | broad data access with filtering, sorting and embedding |
|
|
6
|
+
| | [REST modules](#rest-modules-in-the-builder) | [AutoREST](#autorest-tables-and-views-from-the-sql-workshop) | [PostgREST](#how-it-works) |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
| What | Endpoints you define in the builder: a method, a path and the SQL | A table or view published as it is: rows, one row, insert, update, delete | A separate service that turns a schema of views and functions into an API |
|
|
9
|
+
| Served by | pgkiln itself, under `/a/<alias>/rest/<module>/` | pgkiln itself, under `/a/<alias>/autorest/<object>/` | PostgREST, next to pgkiln |
|
|
10
|
+
| Runs as | the application's database role (like its pages) | the application's database role | a dedicated API role |
|
|
11
|
+
| Good for | a handful of tailored endpoints, field apps, integrations | quick CRUD access to a table, prototypes, simple integrations | broad data access with filtering, sorting and embedding |
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
same row level security, and
|
|
13
|
+
All three use the same tokens (App → REST API: tokens and OAuth clients), the same accounts and the
|
|
14
|
+
same row level security, and all publish an OpenAPI description.
|
|
15
15
|
|
|
16
16
|
## REST modules in the builder
|
|
17
17
|
|
|
@@ -31,11 +31,12 @@ answer 404.
|
|
|
31
31
|
|---|---|
|
|
32
32
|
| `method` | `GET`, `POST`, `PUT`, `PATCH` or `DELETE` |
|
|
33
33
|
| `path` | Segments with `:parameters`, e.g. `orders/:id/lines` |
|
|
34
|
-
| `type` | `collection`: a SELECT, returned page by page as `{items, offset, limit, has_more}` (`?limit=` up to 500, `?offset=`, `page_size` for the default); the page streams from a cursor as a chunked JSON array (an error before the first rows is still a 4xx/5xx, a failure after them ends the response short) · `item`: one row as an object, 404 when there is none · `sql`: statements; the first row of the last one is the response (201 for POST, 204 without a row; `status` overrides) |
|
|
34
|
+
| `type` | `collection`: a SELECT, returned page by page as `{items, offset, limit, has_more}` (`?limit=` up to 500, `?offset=`, `page_size` for the default); the page streams from a cursor as a chunked JSON array (an error before the first rows is still a 4xx/5xx, a failure after them ends the response short) · `item`: one row as an object, 404 when there is none · `sql`: statements; the first row of the last one is the response (201 for POST, 204 without a row; `status` overrides) · `plpgsql` (sprint 104): a PL/pgSQL block that answers with `meta.rest_reply(body, status)` ([below](#modules-templates-and-handlers-in-the-sql-workshop)) · `media`: a query whose row has `content_type`, `content` and optionally `filename`, sent as that content |
|
|
35
35
|
| `source` | The SQL. Binds: path parameters, query parameters and the fields of a JSON (or form) body, upper case (`:ID`, `:CUSTOMER`); `:BODY` is the whole JSON body. `:APP_USER` (the caller), `:APP_ID` and `:APP_ALIAS` are set by the server: a parameter of that name (or `APP_SESSION`, `APP_PAGE_ID`, `REQUEST`, `APP_LANGUAGE`) is ignored, and can't be a path parameter |
|
|
36
36
|
| `roles` | The caller needs one of these roles (as `meta.has_role()` sees them) |
|
|
37
37
|
| `auth` | `token` (default) or `public` (no token; the SQL runs with `meta.app_user()` = `nobody`) |
|
|
38
38
|
| `description` | For the OpenAPI description |
|
|
39
|
+
| `params` | (sprint 104) Documented parameters: `[{"name": "start_date", "in": "body", "type": "string", "required": true, "description": "…"}]` (`in`: `query` or `body`; types `string`, `integer`, `number`, `boolean`). They go into the OpenAPI document; a required one that is missing or empty answers 400 |
|
|
39
40
|
|
|
40
41
|
**Calling them.** Send `Authorization: Bearer <token>` with a token from **App → REST API**: a token
|
|
41
42
|
issued for an account, or one from `POST /oauth/token` for an OAuth client ([below](#tokens)). An
|
|
@@ -63,6 +64,128 @@ curl -X POST http://127.0.0.1:3100/a/hr/rest/v1/leave -H "Authorization: Bearer
|
|
|
63
64
|
-H "Content-Type: application/json" -d '{"start_date": "2027-05-03", "end_date": "2027-05-04", "reason": "dentist"}'
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
## Modules, templates and handlers in the SQL Workshop
|
|
68
|
+
|
|
69
|
+
**SQL Workshop → RESTful Services → Modules** (sprint 104; Oracle Database Actions / ORDS: REST › Modules) edits
|
|
70
|
+
the same REST modules the ORDS way, for every application of the workspace:
|
|
71
|
+
|
|
72
|
+
- a **module** is a base path, `/a/<alias>/rest/<name>/`, with a title, a description, *Enabled* and its
|
|
73
|
+
**privilege**: application roles (as `meta.has_role()` sees them) of which a caller needs one for *every*
|
|
74
|
+
handler of the module, public ones included;
|
|
75
|
+
- a **template** is a URI below it with `:parameters`, e.g. `emp/:id`, with a description for the OpenAPI document
|
|
76
|
+
(a template can exist before its first handler; renaming it moves its handlers);
|
|
77
|
+
- a **handler** answers one method of a template: its source type (*collection query*, *single row*, *SQL*,
|
|
78
|
+
*PL/pgSQL*, *media*), the source, who may call it (a token, or explicitly *public*), further roles, the page size
|
|
79
|
+
or status, and documented parameters, one per line: `start_date body string required First day`.
|
|
80
|
+
|
|
81
|
+
The tree on the left of each page shows module › templates › handlers. A Run only or locked application refuses
|
|
82
|
+
changes. The module's page checks the role the handlers run as and links the module's **OpenAPI** document.
|
|
83
|
+
|
|
84
|
+
**PL/pgSQL handlers** run their block as a `DO` statement as the application's database role. A `DO` block takes
|
|
85
|
+
no parameters, so pgkiln sets the request's binds as transaction-local settings (with query parameters: values
|
|
86
|
+
never become SQL text) and turns `:NAME` in the block into `meta.rest_param('NAME')` (text, NULL when absent):
|
|
87
|
+
|
|
88
|
+
```sql
|
|
89
|
+
declare
|
|
90
|
+
v_id int;
|
|
91
|
+
begin
|
|
92
|
+
v_id := sales.create_order(:CUSTOMER, :TOTAL::numeric);
|
|
93
|
+
perform meta.rest_reply(jsonb_build_object('id', v_id), 201);
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`meta.rest_reply(body jsonb, status int)` sets the response (status 200–599, also 4xx for your own errors; the
|
|
98
|
+
transaction is still committed); without it the handler answers 204. A `raise exception` answers 400 with its
|
|
99
|
+
message and rolls back. Statements without `begin … end` are wrapped in one.
|
|
100
|
+
|
|
101
|
+
**Media handlers** run a query whose first row has `content_type`, `content` (bytea or text) and optionally
|
|
102
|
+
`filename`; the response is that content, sent with `X-Content-Type-Options: nosniff` and a sandboxing
|
|
103
|
+
Content-Security-Policy, so HTML or SVG from a table can't run script in the application's origin. No row: 404.
|
|
104
|
+
|
|
105
|
+
**Try it.** A handler's page sends a request to the real endpoint, with path parameters, a query string and a JSON
|
|
106
|
+
body, either without a token (to see the 401) or *as* an account of the application with a short-lived token (the
|
|
107
|
+
same as issuing one on App → REST API). A PUT, POST or DELETE changes data. The response's status and body are
|
|
108
|
+
shown in the page; it works without JavaScript.
|
|
109
|
+
|
|
110
|
+
**Example:** the HR example's module `hr` (`examples/hr/hr_62_rest_module.sql`): `GET emp/:id` (single row),
|
|
111
|
+
`GET emp/:id/photo` (media), `POST leave` (PL/pgSQL with body parameters) and `GET payroll/:deptno` (roles manager or
|
|
112
|
+
admin):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl http://127.0.0.1:3100/a/hr/rest/hr/emp/7839 -H "Authorization: Bearer $TOKEN"
|
|
116
|
+
curl -X POST http://127.0.0.1:3100/a/hr/rest/hr/leave -H "Authorization: Bearer $TOKEN" \
|
|
117
|
+
-H "Content-Type: application/json" -d '{"start_date": "2027-05-03", "end_date": "2027-05-04", "reason": "dentist"}'
|
|
118
|
+
# {"id": 12, "status": "requested"}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Handlers never run as a superuser, a role that bypasses row level security or pgkiln's owner: such a request
|
|
122
|
+
answers 403 (set `RUNTIME_DATABASE_URL` and give the application its own database role).
|
|
123
|
+
|
|
124
|
+
## AutoREST: tables and views from the SQL Workshop
|
|
125
|
+
|
|
126
|
+
**SQL Workshop → RESTful Services** (`/builder/sql/rest`; Database Actions: a table's *REST › Enable*, ORDS
|
|
127
|
+
AutoREST), or **REST › Enable / Disable** in a table's or view's ⋯ menu (SQL Worksheet navigator, Object Browser).
|
|
128
|
+
Choose:
|
|
129
|
+
|
|
130
|
+
| Setting | Meaning |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Table or view | Any table, partitioned table, view, materialized view or foreign table of a schema the SQL Workshop may use (not `meta`) |
|
|
133
|
+
| Application | An application of the current workspace with a database role. It decides how requests run (below) and gives the URL |
|
|
134
|
+
| URL name | The segment in `/a/<alias>/autorest/<name>/`: lower case letters, digits, `_` and `-`; the object's name by default |
|
|
135
|
+
| Methods | `GET` (rows and one row), `POST` (insert), `PUT` (update), `DELETE`. `PUT` and `DELETE` need a primary key; a materialized view answers `GET` only |
|
|
136
|
+
| Who may call it | **A bearer token of the application** (the default), or **public**: anyone, without a token. Public is never implied: it is a choice on the form |
|
|
137
|
+
| Roles | Optional application roles; the caller needs one of them (`meta.has_role()`), else 403 |
|
|
138
|
+
| Page size | Rows per page without `?limit=` (1–500, default 25) |
|
|
139
|
+
|
|
140
|
+
The object's page shows the checks (the role is not the owner and doesn't bypass row level security; its grants per
|
|
141
|
+
method; row level security on or off; for a view whether it is `security_invoker`; the primary key; columns the role
|
|
142
|
+
may not read), the endpoints with their URLs, cURL and `fetch` examples, and the OpenAPI link. Its settings can be
|
|
143
|
+
changed, the object disabled (404) or removed (*Disable REST*). The table itself is never changed.
|
|
144
|
+
|
|
145
|
+
**Endpoints** (`/a/hr/autorest/dept/` for `hr.dept` published through `hr`):
|
|
146
|
+
|
|
147
|
+
| Request | Answer |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `GET …/dept/` | `{"items": […], "offset": 0, "limit": 25, "has_more": true}`. `?limit=` (1–500) and `?offset=` page; `?<column>=<value>` keeps rows whose column equals the value (several are combined with AND); `?q=<text>` keeps rows where a text column contains the text (case-insensitive; `%` and `_` are literal); `?order=dname,deptno.desc` sorts (default: the primary key). An unknown parameter or column is a 400 |
|
|
150
|
+
| `GET …/dept/10` | The row as an object, or 404. A composite key is comma separated: `…/pair/1,x` (URL-encode a comma inside a value) |
|
|
151
|
+
| `POST …/dept/` | A JSON object of column values → 201 with the new row and a `Location` header. Columns left out get their defaults; an unknown column is a 400 |
|
|
152
|
+
| `PUT …/dept/10` | A JSON object of the columns to change → 200 with the row, or 404 (also when row level security hides it). Columns left out keep their values |
|
|
153
|
+
| `DELETE …/dept/10` | 204, or 404 |
|
|
154
|
+
| `GET /a/hr/autorest/openapi.json` | OpenAPI 3 of the application's published objects: without a token the public ones, with a valid token all of them; column types as JSON Schema |
|
|
155
|
+
|
|
156
|
+
A method that isn't enabled answers 405 with an `Allow` header. Errors are JSON `{"error": "…"}`, as for REST modules:
|
|
157
|
+
400 for invalid input, a check or unique constraint, or a `raise exception`; 403 for a missing role or grant
|
|
158
|
+
(including column grants and a row level security `with check`).
|
|
159
|
+
|
|
160
|
+
**How a request runs.** As for [REST modules](#rest-modules-in-the-builder): the token is checked (signature, expiry,
|
|
161
|
+
the application, an active account with access or a client that isn't revoked) at every request; a browser session is
|
|
162
|
+
not accepted. The statement then runs in a transaction **as the application's database role**, with
|
|
163
|
+
`meta.app_user()` = the caller (`nobody` for a public object without a token), `meta.has_role()` reading the caller's
|
|
164
|
+
roles, and a 30-second statement timeout. So the role's grants, column grants and the table's row level security
|
|
165
|
+
apply exactly as on the application's pages. Columns the role may not select are left out of every answer and can't
|
|
166
|
+
be filtered or sorted on. AutoREST refuses, when the object is enabled and again at every request (after `SET ROLE`),
|
|
167
|
+
a role that owns the object or is a member of its owner (row level security doesn't apply to the owner), a superuser
|
|
168
|
+
and a role with `BYPASSRLS`. An application without a database role can't publish objects. Names come from the
|
|
169
|
+
catalog and are quoted; filter values, keys and bodies are query parameters, never SQL text.
|
|
170
|
+
|
|
171
|
+
The workspace preference *Enable RESTful services* turns AutoREST off with the REST modules (404), and the
|
|
172
|
+
application's availability applies too.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
TOKEN=… # App → REST API → Issue a token for king
|
|
176
|
+
curl "http://127.0.0.1:3100/a/hr/autorest/dept/?limit=2" -H "Authorization: Bearer $TOKEN"
|
|
177
|
+
# {"items":[{"deptno":10,"dname":"ACCOUNTING",…},{"deptno":20,…}],"offset":0,"limit":2,"has_more":true}
|
|
178
|
+
curl "http://127.0.0.1:3100/a/hr/autorest/dept/?limit=2" # 401: no token
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Views.** A view without `security_invoker = true` runs with its owner's rights, so the row level security of the
|
|
182
|
+
tables beneath it does not apply to the caller; the page warns about it. Views have no primary key: they publish the
|
|
183
|
+
collection (`GET`, and `POST` when PostgreSQL can insert into the view).
|
|
184
|
+
|
|
185
|
+
**Export.** The published objects are installation data, like OAuth clients: they name objects of this database and
|
|
186
|
+
are not part of an application export; an in-place import (`pgkiln import --replace`) keeps them. Tailored endpoints
|
|
187
|
+
that travel with the application are [REST modules](#rest-modules-in-the-builder).
|
|
188
|
+
|
|
66
189
|
## PostgREST
|
|
67
190
|
|
|
68
191
|
[PostgREST](https://postgrest.org) runs as a separate service **next to** pgkiln and turns
|
|
@@ -19,7 +19,7 @@ setup`) and on practically every managed service. Install with `create extension
|
|
|
19
19
|
| **unaccent** | Accent-insensitive search (`café` finds `cafe`), essential in multilingual apps | Text search options |
|
|
20
20
|
| **citext** | Case-insensitive text columns (e-mail addresses, codes) without `lower()` everywhere | NLS_COMP=LINGUISTIC |
|
|
21
21
|
| **btree_gist** | Exclusion constraints on ranges, e.g. **no overlapping bookings/leave** enforced by the database, race-free | Custom PL/SQL checks |
|
|
22
|
-
| **pg_stat_statements** | Which SQL is slow or called most, per role, so per app (`db_role`) | Monitor Activity → Top SQL |
|
|
22
|
+
| **pg_stat_statements** | Which SQL is slow or called most, per role, so per app (`db_role`) | Monitor Activity → Top SQL; SQL Workshop → SQL Monitor (Top SQL, a statement's totals) |
|
|
23
23
|
| **tablefunc** | `crosstab()` for pivot tables | Interactive report *Pivot* |
|
|
24
24
|
| **ltree** | Hierarchies (org charts, categories, menus) with fast subtree queries | Tree region, `CONNECT BY` |
|
|
25
25
|
| **postgres_fdw**, **file_fdw** | Query another PostgreSQL database, or a CSV file, as if it were a table | Remote servers / database links |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 62: a REST module built in SQL Workshop › RESTful
|
|
3
|
+
-- Services (sprint 104; docs/guide/13-rest-api.md → Modules, templates
|
|
4
|
+
-- and handlers). Module "hr" at /a/hr/rest/hr/, with a token from
|
|
5
|
+
-- App › REST API:
|
|
6
|
+
--
|
|
7
|
+
-- emp/ GET collection query: the active employees
|
|
8
|
+
-- emp/:id GET single row: one employee
|
|
9
|
+
-- emp/:id/photo GET media: the employee's photo
|
|
10
|
+
-- leave POST PL/pgSQL: request leave with
|
|
11
|
+
-- {"start_date": "…", "end_date": "…", "reason": "…"}
|
|
12
|
+
-- payroll/:deptno GET collection query; managers and administrators only
|
|
13
|
+
-- =====================================================================
|
|
14
|
+
insert into meta.rest_module (app_id, name, title, description, templates, handlers)
|
|
15
|
+
select id, 'hr', 'HR (RESTful Services)', 'Employees and leave requests: a module of templates and handlers.',
|
|
16
|
+
$t$[
|
|
17
|
+
{"uri": "emp", "description": "The employees"},
|
|
18
|
+
{"uri": "emp/:id", "description": "One employee by number"},
|
|
19
|
+
{"uri": "emp/:id/photo", "description": "An employee's photo"},
|
|
20
|
+
{"uri": "leave", "description": "Leave requests of the caller"},
|
|
21
|
+
{"uri": "payroll/:deptno", "description": "Payroll of a department"}
|
|
22
|
+
]$t$::jsonb,
|
|
23
|
+
$h$[
|
|
24
|
+
{"method": "GET", "path": "emp", "type": "collection", "description": "The active employees",
|
|
25
|
+
"source": "select empno, initcap(ename) as name, initcap(job) as job, deptno from hr.emp where active order by empno"},
|
|
26
|
+
{"method": "GET", "path": "emp/:id", "type": "item", "description": "One employee",
|
|
27
|
+
"source": "select empno, initcap(ename) as name, initcap(job) as job, deptno, hiredate, mgr from hr.emp where empno = :ID::int"},
|
|
28
|
+
{"method": "GET", "path": "emp/:id/photo", "type": "media", "description": "The employee's photo",
|
|
29
|
+
"source": "select photo_mime as content_type, photo as content, photo_name as filename from hr.emp where empno = :ID::int and photo is not null"},
|
|
30
|
+
{"method": "POST", "path": "leave", "type": "plpgsql", "description": "Request leave (as the caller)",
|
|
31
|
+
"params": [{"name": "start_date", "in": "body", "type": "string", "required": true, "description": "First day (YYYY-MM-DD)"},
|
|
32
|
+
{"name": "end_date", "in": "body", "type": "string", "required": true, "description": "Last day (YYYY-MM-DD)"},
|
|
33
|
+
{"name": "reason", "in": "body", "type": "string"}],
|
|
34
|
+
"source": "declare\n v_id int;\nbegin\n v_id := hr.request_leave(:START_DATE::date, :END_DATE::date, :REASON);\n perform meta.rest_reply(jsonb_build_object('id', v_id, 'status', 'requested'), 201);\nend"},
|
|
35
|
+
{"method": "GET", "path": "payroll/:deptno", "type": "collection", "roles": ["manager", "admin"], "description": "Salaries of a department",
|
|
36
|
+
"source": "select empno, initcap(ename) as name, sal from hr.emp where deptno = :DEPTNO::int and active order by empno"}
|
|
37
|
+
]$h$::jsonb
|
|
38
|
+
from meta.app where alias = 'hr'
|
|
39
|
+
on conflict (app_id, name) do nothing;
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -416,7 +416,7 @@ dialog.region-inline_drawer:target { inset: 0 0 0 auto; }
|
|
|
416
416
|
.req { color: var(--danger); margin-left: 0.2em; }
|
|
417
417
|
input:not([type=checkbox]):not([type=radio]), select, textarea {
|
|
418
418
|
font: inherit; color: var(--text); background: var(--surface);
|
|
419
|
-
border: 1px solid var(--border); border-radius: calc(var(--radius) * 0.75); padding: 0.45rem 0.6rem; width: 100%; min-width: 0;
|
|
419
|
+
border: 1px solid var(--field-border, var(--border)); border-radius: calc(var(--radius) * 0.75); padding: 0.45rem 0.6rem; width: 100%; min-width: 0;
|
|
420
420
|
}
|
|
421
421
|
input:disabled, select:disabled, textarea:disabled { opacity: 0.6; }
|
|
422
422
|
textarea.code { font-family: var(--mono); font-size: 0.84rem; line-height: 1.45; tab-size: 2; }
|
package/public/builder-icons.svg
CHANGED
|
@@ -30,4 +30,6 @@
|
|
|
30
30
|
<symbol id="b-save" viewBox="0 0 24 24"><path d="M5 4h11l3 3v13H5z"/><path d="M8 4v5h7V4M8 20v-6h8v6"/></symbol>
|
|
31
31
|
<symbol id="b-dots" viewBox="0 0 24 24"><path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3.2"/></symbol>
|
|
32
32
|
<symbol id="b-pages" viewBox="0 0 24 24"><path d="M8 3h8l4 4v11H8z"/><path d="M16 3v4h4"/><path d="M4 7v14h11"/></symbol>
|
|
33
|
+
<!-- (sprint 105) the pgkiln brand mark: an ember flame with a hollow core; filled, unlike the stroke icons -->
|
|
34
|
+
<symbol id="b-kiln" viewBox="0 0 24 24"><path fill="currentColor" stroke="none" fill-rule="evenodd" d="M12.3 1.8c.6 3.1 2.5 4.7 4.2 6.6 1.8 1.9 3.1 4 3.1 6.7a7.6 7.6 0 0 1-15.2 0c0-2.6 1.1-4.6 2.8-6.1.2 1.6 1 2.8 2.2 3.4C9.2 8.6 10.5 4.7 12.3 1.8zM12 20.4a2.9 2.9 0 0 0 2.9-2.9c0-1.4-.5-2.9-1.4-4-.8.8-1.3 1.8-1.4 3-.7-.3-1.1-.9-1.3-1.6-.9.9-1.8 1.9-1.8 3.3a3 3 0 0 0 3 2.2z"/></symbol>
|
|
33
35
|
</svg>
|