@owlmeans/server-job 0.1.18-rc.2 → 0.1.18-rc.20
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/README.md +36 -86
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-job/SKILL.md +60 -107
- package/build/actions/cancel.d.ts +3 -4
- package/build/actions/cancel.d.ts.map +1 -1
- package/build/actions/cancel.js +11 -6
- package/build/actions/cancel.js.map +1 -1
- package/build/actions/get.d.ts +3 -4
- package/build/actions/get.d.ts.map +1 -1
- package/build/actions/get.js +6 -4
- package/build/actions/get.js.map +1 -1
- package/build/actions/list.d.ts +3 -4
- package/build/actions/list.d.ts.map +1 -1
- package/build/actions/list.js +10 -12
- package/build/actions/list.js.map +1 -1
- package/build/actions/watch.d.ts +5 -6
- package/build/actions/watch.d.ts.map +1 -1
- package/build/actions/watch.js +21 -20
- package/build/actions/watch.js.map +1 -1
- package/build/helper.d.ts +3 -5
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +9 -11
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +0 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +0 -3
- package/build/index.js.map +1 -1
- package/build/types.d.ts +18 -46
- package/build/types.d.ts.map +1 -1
- package/build/utils/index.d.ts +2 -2
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +2 -2
- package/build/utils/index.js.map +1 -1
- package/build/utils/policy.d.ts +7 -0
- package/build/utils/policy.d.ts.map +1 -0
- package/build/utils/policy.js +10 -0
- package/build/utils/policy.js.map +1 -0
- package/build/utils/view.d.ts +18 -0
- package/build/utils/view.d.ts.map +1 -0
- package/build/utils/view.js +94 -0
- package/build/utils/view.js.map +1 -0
- package/package.json +17 -14
- package/src/actions/cancel.ts +15 -12
- package/src/actions/get.ts +10 -10
- package/src/actions/list.ts +15 -21
- package/src/actions/watch.ts +24 -25
- package/src/helper.ts +10 -18
- package/src/index.ts +0 -3
- package/src/types.ts +21 -48
- package/src/utils/index.ts +2 -2
- package/src/utils/policy.ts +20 -0
- package/src/utils/view.ts +102 -0
- package/tests/view.spec.ts +55 -0
- package/build/consts.d.ts +0 -26
- package/build/consts.d.ts.map +0 -1
- package/build/consts.js +0 -26
- package/build/consts.js.map +0 -1
- package/build/entrypoints.d.ts +0 -21
- package/build/entrypoints.d.ts.map +0 -1
- package/build/entrypoints.js +0 -46
- package/build/entrypoints.js.map +0 -1
- package/build/schemas.d.ts +0 -10
- package/build/schemas.d.ts.map +0 -1
- package/build/schemas.js +0 -17
- package/build/schemas.js.map +0 -1
- package/build/utils/owner.d.ts +0 -33
- package/build/utils/owner.d.ts.map +0 -1
- package/build/utils/owner.js +0 -41
- package/build/utils/owner.js.map +0 -1
- package/build/utils/resource.d.ts +0 -23
- package/build/utils/resource.d.ts.map +0 -1
- package/build/utils/resource.js +0 -28
- package/build/utils/resource.js.map +0 -1
- package/src/consts.ts +0 -28
- package/src/entrypoints.ts +0 -56
- package/src/schemas.ts +0 -19
- package/src/utils/owner.ts +0 -60
- package/src/utils/resource.ts +0 -40
package/README.md
CHANGED
|
@@ -1,101 +1,51 @@
|
|
|
1
1
|
# @owlmeans/server-job
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[`@owlmeans/queue`](../queue) and its driver — nothing here produces or consumes work.
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
- `declareJobEntrypoints(root, opts?)` — the four declarations, for the app's SHARED package
|
|
10
|
-
- `serveJobEntrypoints(entrypoints, root, opts?)` — elevate them with this package's handlers
|
|
11
|
-
- `listJobs` / `getJob` / `cancelJob` / `watchJobs` — the handlers, when an app elevates by hand
|
|
12
|
-
- A caller sees only the jobs it owns; the escape hatch is an option, not a hardcoded permission
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
bun add @owlmeans/server-job@^0.1.18-rc.0
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
Declare the group once, in the package both halves import:
|
|
3
|
+
Server bridge from technical queue records to the sanitized `@owlmeans/job` domain contract.
|
|
4
|
+
Every binding requires an application-owned `JobExposurePolicy`; there is no permissive default.
|
|
23
5
|
|
|
24
6
|
```typescript
|
|
25
|
-
import { declareJobEntrypoints } from '@owlmeans/
|
|
26
|
-
|
|
27
|
-
export const REPORTS = 'reports'
|
|
28
|
-
export const entrypoints = [
|
|
29
|
-
...declareJobEntrypoints(REPORTS, { path: '/reports/jobs', parent: app.api.base }),
|
|
30
|
-
]
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Serve it in the API process, alongside the queue driver it already wires:
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { serveJobEntrypoints } from '@owlmeans/server-job'
|
|
37
|
-
import { appendRedisQueue } from '@owlmeans/redis-queue'
|
|
38
|
-
|
|
39
|
-
appendRedisQueue(context)
|
|
40
|
-
serveJobEntrypoints(entrypoints, REPORTS, { queue: REPORT_QUEUE })
|
|
41
|
-
context.registerEntrypoints(entrypoints)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Enqueue with the owner in the payload — that is what every read here filters on:
|
|
7
|
+
import { declareJobEntrypoints } from '@owlmeans/job'
|
|
8
|
+
import { jobViewOf, serveJobEntrypoints } from '@owlmeans/server-job'
|
|
45
9
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
name: 'report:build',
|
|
49
|
-
data: { owner: req.auth!.profileId ?? req.auth!.userId, target: id },
|
|
10
|
+
export const reportJobs = declareJobEntrypoints('report-jobs', {
|
|
11
|
+
path: '/reports/jobs', parent: api.reports.base,
|
|
50
12
|
})
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
And report progress from the processor, so there is something to watch:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
worker.process(REPORT_QUEUE, 'report:build', async job => {
|
|
57
|
-
for (const [done, page] of pages.entries()) {
|
|
58
|
-
await job.touch()
|
|
59
|
-
await job.progress({ done, total: pages.length })
|
|
60
|
-
await render(page)
|
|
61
|
-
}
|
|
62
|
-
return { url }
|
|
63
|
-
})
|
|
64
|
-
```
|
|
65
13
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`data.owner` (rename it with `ownerField`) is compared against `auth.profileId ?? auth.userId`.
|
|
69
|
-
A job that exists but belongs to someone else answers exactly as an absent one: `UnknownJob`.
|
|
70
|
-
|
|
71
|
-
An operator console passes a predicate instead of a permission name:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
serveJobEntrypoints(entrypoints, REPORTS, {
|
|
14
|
+
export const bindings = serveJobEntrypoints(reportJobs, {
|
|
75
15
|
queue: REPORT_QUEUE,
|
|
76
|
-
|
|
16
|
+
policy: {
|
|
17
|
+
audience: req => ({ userId: requireUserId(req) }),
|
|
18
|
+
where: (audience, query) => ({
|
|
19
|
+
$and: [
|
|
20
|
+
{ 'data.ownerId': audience.userId },
|
|
21
|
+
...(query.kind == null ? [] : [{ name: internalNameOf(query.kind) }]),
|
|
22
|
+
],
|
|
23
|
+
}),
|
|
24
|
+
lookup: async (id, audience, resource) =>
|
|
25
|
+
resource.load({ publicId: id, 'data.ownerId': audience.userId }),
|
|
26
|
+
map: record => jobViewOf(record, {
|
|
27
|
+
id: publicIdOf(record),
|
|
28
|
+
kind: publicKindOf(record),
|
|
29
|
+
summary: publicSummaryOf(record),
|
|
30
|
+
metadata: allowlistedMetadataOf(record),
|
|
31
|
+
result: publicResultOf(record),
|
|
32
|
+
}),
|
|
33
|
+
cancel: (record, audience) => ownerIdOf(record) === audience.userId,
|
|
34
|
+
},
|
|
77
35
|
})
|
|
78
36
|
```
|
|
79
37
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
A `JobEvent` carries no owner, so `watchJobs` attributes each frame by reading its job back. A
|
|
83
|
-
queue configured with `removeOnComplete` has nothing left to read when the completion arrives, and
|
|
84
|
-
an unattributable frame is dropped rather than fanned out. Leave completed jobs in place on any
|
|
85
|
-
queue that is watched.
|
|
86
|
-
|
|
87
|
-
## Depends On
|
|
88
|
-
|
|
89
|
-
- [`@owlmeans/queue`](../queue) — `ctx.jobs(queue)`, `JobRecord`, `JobEvent`, `UnknownJob`
|
|
90
|
-
- [`@owlmeans/server-api`](../server-api) — `handleRequest` / `handleParams`
|
|
91
|
-
- [`@owlmeans/server-socket`](../server-socket) — `handleConnection`
|
|
92
|
-
- [`@owlmeans/server-entrypoint`](../server-entrypoint) — `elevate`
|
|
93
|
-
- [`@owlmeans/auth-common`](../auth-common) — `DEFAULT_GUARD`
|
|
38
|
+
The policy is the security boundary:
|
|
94
39
|
|
|
95
|
-
|
|
40
|
+
- `audience` derives authenticated application scope.
|
|
41
|
+
- `where` translates the closed public query into scoped technical criteria.
|
|
42
|
+
- `lookup` resolves an opaque public id inside that scope.
|
|
43
|
+
- `map` allowlists fields and returns only `JobView`; `jobViewOf` bounds and sanitizes JSON.
|
|
44
|
+
- `cancel` is optional and cancellation is denied unless it explicitly approves.
|
|
96
45
|
|
|
97
|
-
|
|
98
|
-
|
|
46
|
+
List, get, cancel and watch all apply the same policy. Watch frames are reloaded inside the
|
|
47
|
+
audience scope and emitted as `JobViewEvent`; an unattributable event is dropped. Keep completed
|
|
48
|
+
records long enough for watched completion events to be projected.
|
|
99
49
|
|
|
100
50
|
<!-- owlmeans:agent-guidance:start -->
|
|
101
51
|
## Agent guidance
|
|
@@ -105,7 +55,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
105
55
|
your project's skill store (`.agents/skills/`):
|
|
106
56
|
|
|
107
57
|
```sh
|
|
108
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
58
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.33
|
|
109
59
|
```
|
|
110
60
|
|
|
111
61
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-job",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.20",
|
|
5
|
+
"generatedAt": "2026-09-22T18:27:08.242Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-job
|
|
3
|
-
description: How to
|
|
3
|
+
description: How to expose technical queue work as sanitized application JobView data through a mandatory authenticated policy. Auto-invoked when binding @owlmeans/server-job or building UI-visible application job status.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,133 +8,86 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-job
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-job": "^0.1.18-rc.
|
|
12
|
-
|
|
13
|
-
The READ side of a queue. `@owlmeans/queue` and its driver enqueue and process; this package turns
|
|
14
|
-
what they leave behind into four entrypoints an application elevates, so that "a long job reports
|
|
15
|
-
progress to the user's screen" is wiring rather than code.
|
|
16
|
-
|
|
17
|
-
## Key Exports
|
|
18
|
-
|
|
19
|
-
| Export | Description |
|
|
20
|
-
|--------|-------------|
|
|
21
|
-
| `declareJobEntrypoints(root, opts?)` | The four declarations of one job group, for the SHARED package |
|
|
22
|
-
| `jobEntrypointAliases(root)` | `{ base, list, get, cancel, watch }` — the alias shape both halves use |
|
|
23
|
-
| `serveJobEntrypoints(entrypoints, root, opts?)` | Elevate the group with this package's handlers |
|
|
24
|
-
| `listJobs(opts?)` / `getJob(opts?)` / `cancelJob(opts?)` | The HTTP handlers, for elevating by hand |
|
|
25
|
-
| `watchJobs(opts?)` | The socket handler — pushes `JobEvent` frames under `JOB_EVENT` |
|
|
26
|
-
| `jobOwnerOf(req)` / `requireJobOwner(req)` / `jobViewer(req, ctx, opts?)` | Who a request reads as |
|
|
27
|
-
| `jobScope(viewer, opts?)` / `owns(record, viewer, opts?)` / `readOwnedJob(...)` | Applying that to records |
|
|
28
|
-
| `ownerFieldOf(opts?)` / `ownerOf(record, opts?)` | The configured owner field, and what one record says its owner is |
|
|
29
|
-
| `jobsOf(ctx, opts?)` | The `QueueResource` a group reads |
|
|
30
|
-
| `JobListQuerySchema` | The list query's ajv schema, for a filter of your own |
|
|
31
|
-
| `JobEntrypointAliases` / `JobEntrypointOptions` / `JobHandlerOptions` / `JobAdminCheck` / `JobListQuery` | The alias and option shapes |
|
|
32
|
-
| Constants | `DEFAULT_JOB_ROOT` (`jobs`), `DEFAULT_JOB_PATH` (`/jobs`), `DEFAULT_OWNER_FIELD` (`owner`), `DEFAULT_JOB_SORT` (`createdAt`), `JOB_EVENT` (`job-event`) |
|
|
33
|
-
|
|
34
|
-
## Declaring, once, in the shared package
|
|
35
|
-
|
|
36
|
-
A group is a root alias plus a path. Everything under it is derived, so a target app declares it in
|
|
37
|
-
the package its API and its browser both import, and neither side ever writes a path:
|
|
11
|
+
**Install:** `"@owlmeans/server-job": "^0.1.18-rc.20"` in `dependencies`
|
|
38
12
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export const REPORTS = 'reports'
|
|
43
|
-
export const entrypoints = [
|
|
44
|
-
...declareJobEntrypoints(REPORTS, { path: '/reports/jobs', parent: app.api.base }),
|
|
45
|
-
]
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Aliases are `<root>`, `<root>:list`, `<root>:get`, `<root>:cancel`, `<root>:watch`. **That shape,
|
|
49
|
-
and the `job-event` frame name, are the whole contract with `@owlmeans/client-job`** — the two
|
|
50
|
-
packages restate them instead of sharing a module, because this one pulls fastify in and a browser
|
|
51
|
-
bundle must not.
|
|
13
|
+
This package is the boundary between technical `@owlmeans/queue` records and the safe
|
|
14
|
+
`@owlmeans/job` contract. `serveJobEntrypoints` requires a `JobExposurePolicy`; there is no default
|
|
15
|
+
owner field, administrator bypass or raw-record response.
|
|
52
16
|
|
|
53
|
-
|
|
54
|
-
because ownership is derived from the authenticated subject, and an unguarded group has no subject
|
|
55
|
-
to derive it from — `guard: null` is for a group scoped some other way, and its handlers then
|
|
56
|
-
answer `AuthorizationError`. `service` points the group at another app's route; `path` moves it.
|
|
17
|
+
## Declare the public contract
|
|
57
18
|
|
|
58
|
-
|
|
59
|
-
the same call with another root.
|
|
60
|
-
|
|
61
|
-
## Serving
|
|
19
|
+
Declare routes with `@owlmeans/job`, in the narrow package shared by this API and its client:
|
|
62
20
|
|
|
63
21
|
```typescript
|
|
64
|
-
import {
|
|
65
|
-
import { appendRedisQueue } from '@owlmeans/redis-queue'
|
|
66
|
-
|
|
67
|
-
appendRedisQueue(context)
|
|
68
|
-
serveJobEntrypoints(entrypoints, REPORTS, { queue: REPORT_QUEUE })
|
|
69
|
-
context.registerEntrypoints(entrypoints)
|
|
70
|
-
```
|
|
22
|
+
import { declareJobEntrypoints } from '@owlmeans/job'
|
|
71
23
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
the sole declared queue and refuses to guess once there are two. Passing an array that carries no
|
|
75
|
-
group under that root is a `SyntaxError` — the declarations and the serving call must name the same
|
|
76
|
-
root, and they usually do because both read it from one exported constant.
|
|
77
|
-
|
|
78
|
-
## The ownership rule
|
|
79
|
-
|
|
80
|
-
**A caller sees only the jobs it owns**, and ownership lives in the job's own payload — a
|
|
81
|
-
`JobRecord` has no owner column, so the producer writes it:
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
await context.jobs(REPORT_QUEUE).create({
|
|
85
|
-
name: 'report:build',
|
|
86
|
-
data: { owner: req.auth!.profileId ?? req.auth!.userId, target: id },
|
|
24
|
+
export const reportJobs = declareJobEntrypoints('report-jobs', {
|
|
25
|
+
path: '/reports/jobs', parent: api.reports.base,
|
|
87
26
|
})
|
|
88
27
|
```
|
|
89
28
|
|
|
90
|
-
|
|
91
|
-
profile first, the same subject `@owlmeans/server-socket` addresses a connection by, so one profile
|
|
92
|
-
of a multi-profile account does not see another's work.
|
|
29
|
+
These are ordinary guarded HTTP/WS declarations. They carry no queue name or broker option.
|
|
93
30
|
|
|
94
|
-
|
|
95
|
-
does**. Telling the two apart is what turns a broker id space into an enumeration oracle.
|
|
96
|
-
|
|
97
|
-
The escape hatch is a predicate, never a permission name — which permission, gate or role means
|
|
98
|
-
"operator" is the application's decision:
|
|
31
|
+
## Bind the bridge
|
|
99
32
|
|
|
100
33
|
```typescript
|
|
101
|
-
|
|
34
|
+
import { jobViewOf, serveJobEntrypoints } from '@owlmeans/server-job'
|
|
35
|
+
|
|
36
|
+
export const reportJobBindings = serveJobEntrypoints(reportJobs, {
|
|
102
37
|
queue: REPORT_QUEUE,
|
|
103
|
-
|
|
38
|
+
policy: {
|
|
39
|
+
audience: req => ({ userId: requireUserId(req) }),
|
|
40
|
+
where: (audience, query) => ({
|
|
41
|
+
$and: [
|
|
42
|
+
{ 'data.ownerId': audience.userId },
|
|
43
|
+
...(query.kind == null ? [] : [{ name: internalNameOf(query.kind) }]),
|
|
44
|
+
],
|
|
45
|
+
}),
|
|
46
|
+
lookup: async (id, audience, resource) =>
|
|
47
|
+
resource.load({ publicId: id, 'data.ownerId': audience.userId }),
|
|
48
|
+
map: record => jobViewOf(record, {
|
|
49
|
+
id: publicIdOf(record),
|
|
50
|
+
kind: publicKindOf(record),
|
|
51
|
+
summary: publicSummaryOf(record),
|
|
52
|
+
metadata: allowlistedMetadataOf(record),
|
|
53
|
+
result: publicResultOf(record),
|
|
54
|
+
}),
|
|
55
|
+
cancel: (record, audience) => ownerIdOf(record) === audience.userId,
|
|
56
|
+
},
|
|
104
57
|
})
|
|
105
58
|
```
|
|
106
59
|
|
|
107
|
-
|
|
108
|
-
with no options at all is closed rather than open.
|
|
109
|
-
|
|
110
|
-
## What each handler answers
|
|
60
|
+
Every policy part is load-bearing:
|
|
111
61
|
|
|
112
|
-
|
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
62
|
+
| Part | Rule |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `audience` | Derive authenticated application scope; never trust a body owner id |
|
|
65
|
+
| `where` | Translate the closed public query into criteria that always include that scope |
|
|
66
|
+
| `lookup` | Resolve an opaque public id only inside the same scope |
|
|
67
|
+
| `map` | Explicitly allowlist public fields and return `JobView` |
|
|
68
|
+
| `cancel` | Optional; absence or `false` denies cancellation |
|
|
118
69
|
|
|
119
|
-
|
|
70
|
+
`jobViewOf` maps technical states and timestamps, copies only fields the application supplies,
|
|
71
|
+
bounds JSON depth/item/string size, strips prototype keys and non-JSON values, and never copies a
|
|
72
|
+
raw failure. Use `publicJobError()` unless an application-safe message is explicitly available.
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
than fanned out to everyone. **Leave completed jobs in place on any queue that is watched.**
|
|
74
|
+
List, get, cancel and watch all use the same policy. A foreign id answers the same as an absent
|
|
75
|
+
one. Watch reloads each event inside the audience scope and emits only `JobViewEvent`; an event that
|
|
76
|
+
cannot be attributed is dropped. Keep completed records long enough for their completion event to
|
|
77
|
+
be projected.
|
|
126
78
|
|
|
127
|
-
##
|
|
79
|
+
## Key exports
|
|
128
80
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
81
|
+
| Export | Purpose |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `serveJobEntrypoints` | Bind list/get/cancel/watch using a mandatory policy |
|
|
84
|
+
| `listJobs` / `getJob` / `cancelJob` / `watchJobs` | Bind one handler manually |
|
|
85
|
+
| `jobViewOf` | Safe record-to-view mapper |
|
|
86
|
+
| `sanitizeJobJson` / `publicJobError` | Bounded JSON and safe failure helpers |
|
|
87
|
+
| `JobExposurePolicy` / `JobHandlerOptions` | Application policy contracts |
|
|
134
88
|
|
|
135
89
|
## Related
|
|
136
90
|
|
|
137
|
-
- `
|
|
138
|
-
|
|
139
|
-
- `redis-queue` —
|
|
140
|
-
- `client-job` — the browser half; `web-panel` — `./jobs` renders what it collects
|
|
91
|
+
- `job` — the safe shared contract
|
|
92
|
+
- `client-job` — browser state over `JobView`
|
|
93
|
+
- `queue` and `redis-queue` — backend-only execution and storage
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type { JobHandlerOptions } from '../types.js';
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import type { Context, JobEntrypoints, JobHandlerOptions } from '../types.js';
|
|
4
3
|
/**
|
|
5
4
|
* Cancel a job and answer with what was cancelled.
|
|
6
5
|
*
|
|
@@ -11,5 +10,5 @@ import type { JobHandlerOptions } from '../types.js';
|
|
|
11
10
|
*
|
|
12
11
|
* @throws {UnknownJob}
|
|
13
12
|
*/
|
|
14
|
-
export declare const cancelJob: (opts
|
|
13
|
+
export declare const cancelJob: (protocol: JobEntrypoints['cancel'], opts: JobHandlerOptions) => ReturnType<ReturnType<typeof handlers<Context>>['params']>;
|
|
15
14
|
//# sourceMappingURL=cancel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cancel.d.ts","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cancel.d.ts","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG7E;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,aACV,cAAc,CAAC,QAAQ,CAAC,QAC5B,iBAAiB,KACtB,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAUxD,CAAA"}
|
package/build/actions/cancel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import { UnknownJob } from '@owlmeans/queue';
|
|
3
|
+
import { jobsOf, readExposedJob } from '../utils/index.js';
|
|
3
4
|
/**
|
|
4
5
|
* Cancel a job and answer with what was cancelled.
|
|
5
6
|
*
|
|
@@ -10,10 +11,14 @@ import { jobViewer, jobsOf, readOwnedJob } from '../utils/index.js';
|
|
|
10
11
|
*
|
|
11
12
|
* @throws {UnknownJob}
|
|
12
13
|
*/
|
|
13
|
-
export const cancelJob = (opts) =>
|
|
14
|
+
export const cancelJob = (protocol, opts) => handlers().params(protocol, async ({ id }, ctx, req) => {
|
|
14
15
|
const resource = jobsOf(ctx, opts);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const audience = await opts.policy.audience(req, ctx);
|
|
17
|
+
const record = await readExposedJob(resource, id, audience, opts);
|
|
18
|
+
if (opts.policy.cancel == null || !await opts.policy.cancel(record, audience)) {
|
|
19
|
+
throw new UnknownJob('job');
|
|
20
|
+
}
|
|
21
|
+
const removed = await resource.take(record.id);
|
|
22
|
+
return await opts.policy.map(removed, audience);
|
|
18
23
|
});
|
|
19
24
|
//# sourceMappingURL=cancel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cancel.js","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cancel.js","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAE1D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,QAAkC,EAClC,IAAuB,EACqC,EAAE,CAC9D,QAAQ,EAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;IACjE,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAY,CAAC,CAAA;IACxD,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AACjD,CAAC,CAAC,CAAA"}
|
package/build/actions/get.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type { JobHandlerOptions } from '../types.js';
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import type { Context, JobEntrypoints, JobHandlerOptions } from '../types.js';
|
|
4
3
|
/**
|
|
5
4
|
* One job.
|
|
6
5
|
*
|
|
7
6
|
* @throws {UnknownJob} for an id that is absent AND for one that belongs to someone else.
|
|
8
7
|
*/
|
|
9
|
-
export declare const getJob: (opts
|
|
8
|
+
export declare const getJob: (protocol: JobEntrypoints['get'], opts: JobHandlerOptions) => ReturnType<ReturnType<typeof handlers<Context>>['params']>;
|
|
10
9
|
//# sourceMappingURL=get.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG7E;;;;GAIG;AACH,eAAO,MAAM,MAAM,aACP,cAAc,CAAC,KAAK,CAAC,QACzB,iBAAiB,KACtB,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAMxD,CAAA"}
|
package/build/actions/get.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import { jobsOf, readExposedJob } from '../utils/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* One job.
|
|
5
5
|
*
|
|
6
6
|
* @throws {UnknownJob} for an id that is absent AND for one that belongs to someone else.
|
|
7
7
|
*/
|
|
8
|
-
export const getJob = (opts) =>
|
|
8
|
+
export const getJob = (protocol, opts) => handlers().params(protocol, async ({ id }, ctx, req) => {
|
|
9
9
|
const resource = jobsOf(ctx, opts);
|
|
10
|
-
|
|
10
|
+
const audience = await opts.policy.audience(req, ctx);
|
|
11
|
+
const record = await readExposedJob(resource, id, audience, opts);
|
|
12
|
+
return await opts.policy.map(record, audience);
|
|
11
13
|
});
|
|
12
14
|
//# sourceMappingURL=get.js.map
|
package/build/actions/get.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,QAA+B,EAC/B,IAAuB,EACqC,EAAE,CAC9D,QAAQ,EAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;IACjE,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;AAChD,CAAC,CAAC,CAAA"}
|
package/build/actions/list.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type { JobHandlerOptions } from '../types.js';
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import type { Context, JobEntrypoints, JobHandlerOptions } from '../types.js';
|
|
4
3
|
/**
|
|
5
4
|
* The caller's jobs, newest first.
|
|
6
5
|
*
|
|
@@ -9,5 +8,5 @@ import type { JobHandlerOptions } from '../types.js';
|
|
|
9
8
|
* name filters go through the same criteria language as every other resource, so a filter written
|
|
10
9
|
* for this list means the same thing applied to the store the browser holds.
|
|
11
10
|
*/
|
|
12
|
-
export declare const listJobs: (opts
|
|
11
|
+
export declare const listJobs: (protocol: JobEntrypoints['list'], opts: JobHandlerOptions) => ReturnType<ReturnType<typeof handlers<Context>>['request']>;
|
|
13
12
|
//# sourceMappingURL=list.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAG/C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAgB,MAAM,aAAa,CAAA;AAG3F;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,aACT,cAAc,CAAC,MAAM,CAAC,QAC1B,iBAAiB,KACtB,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAY3D,CAAA"}
|
package/build/actions/list.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { jobScope, jobViewer, jobsOf } from '../utils/index.js';
|
|
1
|
+
import { handlers } from '@owlmeans/server-api';
|
|
2
|
+
import { jobsOf } from '../utils/index.js';
|
|
4
3
|
/**
|
|
5
4
|
* The caller's jobs, newest first.
|
|
6
5
|
*
|
|
@@ -9,18 +8,17 @@ import { jobScope, jobViewer, jobsOf } from '../utils/index.js';
|
|
|
9
8
|
* name filters go through the same criteria language as every other resource, so a filter written
|
|
10
9
|
* for this list means the same thing applied to the store the browser holds.
|
|
11
10
|
*/
|
|
12
|
-
export const listJobs = (opts) =>
|
|
11
|
+
export const listJobs = (protocol, opts) => handlers().request(protocol, async (req, ctx) => {
|
|
13
12
|
const resource = jobsOf(ctx, opts);
|
|
14
|
-
const
|
|
13
|
+
const audience = await opts.policy.audience(req, ctx);
|
|
15
14
|
const query = (req.query ?? {});
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
...(query.state != null ? { state: query.state } : {}),
|
|
19
|
-
...(query.name != null ? { name: query.name } : {}),
|
|
20
|
-
};
|
|
21
|
-
return await resource.list(where, {
|
|
22
|
-
sort: [{ field: DEFAULT_JOB_SORT, order: 'desc' }],
|
|
15
|
+
const listed = await resource.list(opts.policy.where(audience, query), {
|
|
16
|
+
sort: [{ field: 'createdAt', order: 'desc' }],
|
|
23
17
|
...(query.size != null ? { size: query.size, page: query.page ?? 0 } : {}),
|
|
24
18
|
});
|
|
19
|
+
return {
|
|
20
|
+
...listed,
|
|
21
|
+
items: await Promise.all(listed.items.map(record => opts.policy.map(record, audience))),
|
|
22
|
+
};
|
|
25
23
|
});
|
|
26
24
|
//# sourceMappingURL=list.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAI/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,QAAgC,EAChC,IAAuB,EACsC,EAAE,CAAC,QAAQ,EAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACzH,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAiB,CAAA;IAC/C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE;QACrE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3E,CAAC,CAAA;IACF,OAAO;QACL,GAAG,MAAM;QACT,KAAK,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC1D,CAAA;AACjC,CAAC,CAAC,CAAA"}
|
package/build/actions/watch.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type { JobHandlerOptions } from '../types.js';
|
|
1
|
+
import { connection } from '@owlmeans/server-socket';
|
|
2
|
+
import type { JobEntrypoints, JobHandlerOptions } from '../types.js';
|
|
4
3
|
/**
|
|
5
4
|
* Push this caller's job lifecycle events down a socket.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Broker events are resolved inside the caller's audience and mapped to {@link JobViewEvent}; raw
|
|
7
|
+
* queue ids, payloads, failures and ownership fields never cross the socket boundary.
|
|
9
8
|
*
|
|
10
9
|
* **A `JobEvent` carries no owner**, so each one is attributed by reading its job back, and the
|
|
11
10
|
* ids that answered are remembered for the life of the connection. A queue configured with
|
|
@@ -13,5 +12,5 @@ import type { JobHandlerOptions } from '../types.js';
|
|
|
13
12
|
* attributed by is gone by the time the event arrives, and an unattributable event is dropped
|
|
14
13
|
* rather than fanned out to everyone. Leave completed jobs in place on any queue that is watched.
|
|
15
14
|
*/
|
|
16
|
-
export declare const watchJobs: (opts
|
|
15
|
+
export declare const watchJobs: (protocol: JobEntrypoints['watch'], opts: JobHandlerOptions) => ReturnType<typeof connection>;
|
|
17
16
|
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/actions/watch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/actions/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAMpD,OAAO,KAAK,EAAW,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG7E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,aACV,cAAc,CAAC,OAAO,CAAC,QAC3B,iBAAiB,KACtB,UAAU,CAAC,OAAO,UAAU,CAwC7B,CAAA"}
|