@t4h.framework/vite-panel-plugin 0.0.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @t4h.framework/vite-panel-plugin
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#151](https://github.com/tech4humans-brasil/framework/pull/151) [`d44eb5e`](https://github.com/tech4humans-brasil/framework/commit/d44eb5e1797f523612c9e2f560b0bb583ab8688f) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - host owns panel config and run rejection lives in output
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#148](https://github.com/tech4humans-brasil/framework/pull/148) [`a3696b4`](https://github.com/tech4humans-brasil/framework/commit/a3696b48b48598eda9e51c53538a6086abe36018) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Initialize package
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tech for humans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,14 +7,17 @@ them, input, output, duration, logs, the activity that failed), and an inbox of
7
7
  activities waiting for an external advance.
8
8
 
9
9
  It ships with `@t4h.framework/dev`, which plugs it into the Vite instance it
10
- runs in middleware mode, so there is nothing to install or configure: run
11
- `framework dev` and open the printed base URL. API paths (`/projects/*`,
12
- `/_dev/*`) keep going to the dev server; everything else is the panel.
10
+ runs in middleware mode and writes the configuration of its own server, so
11
+ there is nothing to install or configure: run `framework dev` and open the
12
+ printed base URL. API paths (`/projects/*`, `/_dev/*`) keep going to the dev
13
+ server; everything else is the panel.
13
14
 
14
15
  ## Hosting the panel elsewhere
15
16
 
16
17
  The package is a Vite plugin (`apply: 'serve'`) that serves the pre-built SPA
17
- from `dist/client` and injects the host configuration in the page:
18
+ from `dist/client` and injects the host configuration in the page. The panel
19
+ has no defaults: the host writes the whole configuration, and what it leaves
20
+ out it does not have.
18
21
 
19
22
  ```ts
20
23
  import panel from '@t4h.framework/vite-panel-plugin'
@@ -27,7 +30,7 @@ panel({
27
30
  project: 'billing',
28
31
  metadata: { tenant: 'acme', version: 'v3' },
29
32
  fields: {
30
- run: 'input,output,rejectedReason,activities(pendingStatus,childrenCount)',
33
+ run: 'input,output,activities(pendingStatus,childrenCount)',
31
34
  runActivity: 'pendingStatus,childrenCount,input,output,pendings,children',
32
35
  },
33
36
  capabilities: {
@@ -50,20 +53,20 @@ panel({
50
53
  })
51
54
  ```
52
55
 
53
- | Option | Default (local dev server) | Meaning |
54
- | ---------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | `title` | `Framework Panel` | Name shown in the sidebar and the browser tab |
56
- | `basePath` | `''` (root) | Path the panel is mounted at; the plugin only answers under it and injects `<base href>` so the relative asset URLs resolve |
57
- | `apiUrl` | `/_dev` | Where the panel API described below lives, exactly as the panel should call it: a same-origin path or an absolute URL, with the route paths appended |
58
- | `project` | the manifest id | Project (app) id, the one id the core defines; shown in the top bar and filled into `:project` of the mirror routes |
59
- | `metadata` | `{}` | `{ [key]: string }` labels of the host about this panel (the runtime sends `tenant` and `version`); each one is a box in the top bar and fills the `:key` of the same name in the routes |
60
- | `fields` | `{}` | The `q` the panel sends on `api.runs` (`runs`), `api.run` (`run`) and `api.runActivity` (`runActivity`), in the host's own field-selection grammar; omitted, no `q` is sent |
61
- | `api` | the paths of the local dev server (`/manifest`, `/runs/:run`, `/runs/:run/activities/:activity`, …) | Where each read and write of the panel API lives under `apiUrl`, as path templates with `:workflow`, `:run` and `:activity`; the panel knows what it asks, not where a host mounts it |
62
- | `capabilities.trigger` | `true` | The host exposes `POST {apiUrl}/workflows/:id/runs` without authorization; otherwise the Run button becomes "Copy as curl" of the mirror route |
63
- | `capabilities.advance` | `true` | Same for `POST {apiUrl}/activities/:id/advance` and the inbox |
64
- | `capabilities.events` | `true` | The host streams `GET {apiUrl}/events`; otherwise the panel never polls and the top bar gets a Refresh button |
65
- | `capabilities.rebuild` | `true` | The host rebuilds the app and starts over, like the dev server: the top bar shows the build state and empty lists mention the last build; off for a host that keeps its runs |
66
- | `routes` | `POST /projects/:project/workflows/:workflow/runs` and `PATCH .../runs/:run/activities/:activity` | `{ method, path, headers? }` of the authorized routes a client must call, shown as `curl` examples with those headers; `path` is expanded with `project` and `metadata` |
56
+ | Option | Local dev server (`@t4h.framework/dev`) | Meaning |
57
+ | ---------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `title` | `Framework Panel` | Name shown in the sidebar and the browser tab |
59
+ | `basePath` | `''` (root) | Path the panel is mounted at; the plugin only answers under it and injects `<base href>` so the relative asset URLs resolve |
60
+ | `apiUrl` | `/_dev` | Where the panel API described below lives, exactly as the panel should call it: a same-origin path or an absolute URL, with the route paths appended |
61
+ | `project` | absent; the manifest id fills in | Project (app) id, the one id the core defines; shown in the top bar and filled into `:project` of the mirror routes. The only optional value |
62
+ | `metadata` | `{}` | `{ [key]: string }` labels of the host about this panel (the runtime sends `tenant` and `version`); each one is a box in the top bar and fills the `:key` of the same name in the routes |
63
+ | `fields` | `{}` | The `q` the panel sends on `api.runs` (`runs`), `api.run` (`run`) and `api.runActivity` (`runActivity`), in the host's own field-selection grammar; omitted, no `q` is sent |
64
+ | `api` | `/manifest`, `/state`, `/runs`, `/runs/:run`, `/runs/:run/activities/:activity`, … | Where each read and write of the panel API lives under `apiUrl`, as path templates with `:workflow`, `:run` and `:activity`. `state`, `runWorkflow`, `advanceActivity` and `events` are only called behind their capability, so a host without it leaves them out |
65
+ | `capabilities.trigger` | `true` | The host exposes `POST {apiUrl}{api.runWorkflow}` without authorization; otherwise the Run button becomes "Copy as curl" of the mirror route |
66
+ | `capabilities.advance` | `true` | Same for `POST {apiUrl}{api.advanceActivity}` and the inbox |
67
+ | `capabilities.events` | `true` | The host streams `GET {apiUrl}{api.events}`; otherwise the panel never polls and the top bar gets a Refresh button with the time of the last answer |
68
+ | `capabilities.rebuild` | `true` | The host rebuilds the app and starts over, like the dev server: the top bar shows the build state and empty lists mention the last build; off for a host that keeps its runs |
69
+ | `routes` | `POST /projects/:project/workflows/:workflow/runs` and `PATCH .../runs/:run/activities/:activity` | `{ method, path, headers? }` of the authorized routes a client must call, shown as `curl` examples with those headers; `path` is expanded with `project` and `metadata` |
67
70
 
68
71
  The configuration is read from `window.__T4H_PANEL__`, written by the plugin in
69
72
  place of the `<!--panel-config-->` marker of `index.html`; `<!--panel-base-->`
@@ -80,7 +83,6 @@ import {
80
83
  createPanelAssetsHandler,
81
84
  createPanelHandler,
82
85
  render,
83
- resolvePanelConfig,
84
86
  } from '@t4h.framework/vite-panel-plugin/node'
85
87
  ```
86
88
 
@@ -88,8 +90,7 @@ import {
88
90
  | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
89
91
  | `createPanelHandler` | The whole panel as one `node:http` middleware — the page for a navigation under `basePath`, the assets for everything else. What the plugin uses |
90
92
  | `createPanelAssetsHandler` | The hashed assets, cached forever, for a host that mounts them at a shared public path; `index: false` so a miss falls through |
91
- | `render` | `index.html` with the config and the `<base href>` in place; `{ baseHref }` overrides the default `` `${basePath}/` `` |
92
- | `resolvePanelConfig` | Fills the local defaults into a partial config, what `render` expects; `createPanelHandler` does it for you |
93
+ | `render` | `index.html` with the whole `PanelConfig` and the `<base href>` in place; `{ baseHref }` overrides the default `` `${basePath}/` `` |
93
94
  | `PANEL_CLIENT_DIST` | `dist/client`, the built SPA |
94
95
  | `PANEL_CLIENT_ASSETS_DIR` | `dist/client/assets`, safe to serve publicly and cache forever — the names are hashed and `index.html` is not in there |
95
96
  | `assertPanelBuilt` | Throws when the package was not built |
@@ -103,13 +104,28 @@ app.use('/panel/assets', createPanelAssetsHandler())
103
104
 
104
105
  // inside the authenticated controller of `.../versions/:version/panel`
105
106
  return render(
106
- resolvePanelConfig({
107
+ {
108
+ title: 'Framework Panel',
107
109
  basePath: `/tenants/${tenant}/projects/${project}/versions/${version}/panel`,
108
- apiUrl: `/tenants/${tenant}/projects/${project}/versions/${version}/panel/api`,
110
+ apiUrl: `/tenants/${tenant}/projects/${project}/versions/${version}`,
109
111
  project,
110
112
  metadata: { tenant, version },
111
- capabilities: { trigger: false, advance: false, events: false },
112
- }),
113
+ fields: { run: 'input,output' },
114
+ api: {
115
+ manifest: '/manifest.json',
116
+ runs: '/runs',
117
+ run: '/runs/:run' /* … */,
118
+ },
119
+ capabilities: {
120
+ trigger: false,
121
+ advance: false,
122
+ events: false,
123
+ rebuild: false,
124
+ },
125
+ routes: {
126
+ /* the authorized routes, shown as curl */
127
+ },
128
+ },
113
129
  { baseHref: '/panel/' },
114
130
  )
115
131
  ```
@@ -129,34 +145,37 @@ matched alongside, so the panel never holds more than one page. Child runs are
129
145
  the exception: "show more" under an activity fetches the next page of 15 and
130
146
  keeps the ones already on screen.
131
147
 
132
- | Method | Route | Answer |
133
- | ------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
- | `GET` | `/manifest` | `{ id, description, workflows: [{ id, description, schema, authorization? }], authorization? }`; a host may leave `authorization` out |
135
- | `GET` | `/state` | `{ state: 'idle' \| 'starting' \| 'ready' \| 'exited' }`; only called with `capabilities.rebuild` |
136
- | `GET` | `/runs?status&workflow&id&parentRunId&parentActivityId&rootOnly&createdAfter&createdBefore&limit&offset` | `{ count, runs: Run[] }` newest first; `id` is a prefix, `createdAfter`/`createdBefore` are epoch milliseconds |
137
- | `GET` | `/runs/:id?q` | `Run & { input, output, rejectedReason }`; `q` is `fields.run` |
138
- | `GET` | `/runs/:id/activities?limit&offset&q` | `{ count, activities: Activity[] }` newest first; `q` is `fields.runActivities` |
139
- | `GET` | `/runs/:id/activities/:activityId?q` | `Activity & { input, output }`; `q` is `fields.runActivity` |
140
- | `GET` | `/runs/:id/activities/:activityId/pendings?limit&offset&q` | `{ count, pendings: Pending[] }` newest first; `q` is `fields.runActivityPendings` |
141
- | `GET` | `/runs/:id/logs?limit&offset` | `{ count, logs: [{ level, args, at }] }` newest first |
142
- | `GET` | `/activities/waiting?limit&offset` | `{ count, activities: [{ id, pendingId, runId, workflowId, type, schema, timeoutAt }] }` newest first |
143
- | `GET` | `/events` | SSE: `manifest`, `build:status`, `build:fail`, `app:exit`, `run:created`, `run:updated` (both with a `Run`), `ping`; only opened with `capabilities.events` |
144
- | `POST` | `/workflows/:workflowId/runs` | Only with `capabilities.trigger`; body is the input, answers `201 { id }` |
145
- | `POST` | `/activities/:activityId/advance` | Only with `capabilities.advance`; body `{ payload }`, answers `{ id, type, status }` |
146
-
147
- `Run` is `{ id, workflow, status, rejectedActivityId, parentWorkflowRunId,
148
- parentWorkflowRunActivityId, createdAt, updatedAt }` with `status` in
149
- `running | fulfilled | rejected`. `Activity` is `{ id, type, kind, createdAt }`
148
+ | Method | Route | Answer |
149
+ | ------ | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
150
+ | `GET` | `/manifest` | `{ id, description, workflows: [{ id, description, schema, authorization? }], authorization? }`; a host may leave `authorization` out |
151
+ | `GET` | `/state` | `{ state: 'idle' \| 'starting' \| 'ready' \| 'exited' }`; only called with `capabilities.rebuild` |
152
+ | `GET` | `/runs?status&workflow&id&parentRun&parentActivity&rootOnly&createdAfter&createdBefore&limit&offset` | `{ count, runs: Run[] }` newest first; `id` is a prefix, `createdAfter`/`createdBefore` are ISO 8601 |
153
+ | `GET` | `/runs/:id?q` | `Run & { input, output }`; `q` is `fields.run` |
154
+ | `GET` | `/runs/:id/activities?limit&offset&q` | `{ count, activities: Activity[] }` newest first; `q` is `fields.runActivities` |
155
+ | `GET` | `/runs/:id/activities/:activityId?q` | `Activity & { input, output }`; `q` is `fields.runActivity` |
156
+ | `GET` | `/runs/:id/activities/:activityId/pendings?limit&offset&q` | `{ count, pendings: Pending[] }` newest first; `q` is `fields.runActivityPendings` |
157
+ | `GET` | `/runs/:id/logs?limit&offset` | `{ count, logs: [{ level, args, at }] }` newest first |
158
+ | `GET` | `/activities/waiting?limit&offset` | `{ count, activities: [{ id, pending, run, workflowId, type, schema, timeoutAt }] }` newest first |
159
+ | `GET` | `/events` | SSE: `manifest`, `build:status`, `build:fail`, `app:exit`, `run:created`, `run:updated` (both with a `Run`), `ping`; only opened with `capabilities.events` |
160
+ | `POST` | `/workflows/:workflowId/runs` | Only with `capabilities.trigger`; body is the input, answers `201 { id }` |
161
+ | `POST` | `/activities/:activityId/advance` | Only with `capabilities.advance`; body `{ payload }`, answers `{ id, type, status }` |
162
+
163
+ `Run` is `{ id, workflow, status, parentWorkflowRun,
164
+ parentWorkflowRunActivity, createdAt, updatedAt }` with `status` in
165
+ `running | fulfilled | rejected`. A rejected run or activity carries its
166
+ reason as `output`: an error-shaped one (`{ message, stack? }`) is shown as
167
+ text, anything else as JSON, and the activity that failed is the one with
168
+ `status: 'rejected'`. `Activity` is `{ id, type, kind, createdAt }`
150
169
  plus, on request, `hasChildren`, `input` and `output`; an async one (`kind:
151
170
  'async'`) also carries `completedAt` and, on request, `pendingStatus` (status
152
- of its last pending). `Pending` is `{ id, previousId, status, schema, timeoutAt,
171
+ of its last pending). `Pending` is `{ id, previous, status, schema, timeoutAt,
153
172
  result, payload, createdAt, updatedAt }`.
154
173
 
155
174
  No answer embeds a list: a run does not carry its activities, an activity does
156
175
  not carry its pendings or the runs it started. Each is its own paged route,
157
176
  newest first, and the trace reads one page at a time when the reader gets
158
177
  there: the activities of the run, the pendings of an open async activity, and
159
- the runs an activity started as `GET /runs?parentActivityId=…`, 15 at a time
178
+ the runs an activity started as `GET /runs?parentActivity=…`, 15 at a time
160
179
  behind "show more", since a `startWorkflowMany` can fan out to hundreds of
161
180
  thousands. A sync activity records no completion: the trace
162
181
  takes the next activity starting, or the run ending, as its end. Every listing