@owlmeans/server-job 0.1.18-rc.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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/agent-meta/manifest.json +16 -0
  4. package/agent-meta/skills/server-job/SKILL.md +140 -0
  5. package/build/actions/cancel.d.ts +15 -0
  6. package/build/actions/cancel.d.ts.map +1 -0
  7. package/build/actions/cancel.js +19 -0
  8. package/build/actions/cancel.js.map +1 -0
  9. package/build/actions/get.d.ts +10 -0
  10. package/build/actions/get.d.ts.map +1 -0
  11. package/build/actions/get.js +12 -0
  12. package/build/actions/get.js.map +1 -0
  13. package/build/actions/index.d.ts +5 -0
  14. package/build/actions/index.d.ts.map +1 -0
  15. package/build/actions/index.js +5 -0
  16. package/build/actions/index.js.map +1 -0
  17. package/build/actions/list.d.ts +13 -0
  18. package/build/actions/list.d.ts.map +1 -0
  19. package/build/actions/list.js +26 -0
  20. package/build/actions/list.js.map +1 -0
  21. package/build/actions/watch.d.ts +17 -0
  22. package/build/actions/watch.d.ts.map +1 -0
  23. package/build/actions/watch.js +57 -0
  24. package/build/actions/watch.js.map +1 -0
  25. package/build/consts.d.ts +26 -0
  26. package/build/consts.d.ts.map +1 -0
  27. package/build/consts.js +26 -0
  28. package/build/consts.js.map +1 -0
  29. package/build/entrypoints.d.ts +21 -0
  30. package/build/entrypoints.d.ts.map +1 -0
  31. package/build/entrypoints.js +46 -0
  32. package/build/entrypoints.js.map +1 -0
  33. package/build/helper.d.ts +14 -0
  34. package/build/helper.d.ts.map +1 -0
  35. package/build/helper.js +21 -0
  36. package/build/helper.js.map +1 -0
  37. package/build/index.d.ts +8 -0
  38. package/build/index.d.ts.map +1 -0
  39. package/build/index.js +7 -0
  40. package/build/index.js.map +1 -0
  41. package/build/schemas.d.ts +10 -0
  42. package/build/schemas.d.ts.map +1 -0
  43. package/build/schemas.js +17 -0
  44. package/build/schemas.js.map +1 -0
  45. package/build/types.d.ts +60 -0
  46. package/build/types.d.ts.map +1 -0
  47. package/build/types.js +2 -0
  48. package/build/types.js.map +1 -0
  49. package/build/utils/index.d.ts +3 -0
  50. package/build/utils/index.d.ts.map +1 -0
  51. package/build/utils/index.js +3 -0
  52. package/build/utils/index.js.map +1 -0
  53. package/build/utils/owner.d.ts +33 -0
  54. package/build/utils/owner.d.ts.map +1 -0
  55. package/build/utils/owner.js +41 -0
  56. package/build/utils/owner.js.map +1 -0
  57. package/build/utils/resource.d.ts +23 -0
  58. package/build/utils/resource.d.ts.map +1 -0
  59. package/build/utils/resource.js +28 -0
  60. package/build/utils/resource.js.map +1 -0
  61. package/package.json +46 -0
  62. package/src/actions/cancel.ts +26 -0
  63. package/src/actions/get.ts +19 -0
  64. package/src/actions/index.ts +4 -0
  65. package/src/actions/list.ts +36 -0
  66. package/src/actions/watch.ts +66 -0
  67. package/src/consts.ts +28 -0
  68. package/src/entrypoints.ts +56 -0
  69. package/src/helper.ts +28 -0
  70. package/src/index.ts +8 -0
  71. package/src/schemas.ts +19 -0
  72. package/src/types.ts +66 -0
  73. package/src/utils/index.ts +2 -0
  74. package/src/utils/owner.ts +60 -0
  75. package/src/utils/resource.ts +40 -0
  76. package/tsconfig.json +16 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
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 ADDED
@@ -0,0 +1,114 @@
1
+ # @owlmeans/server-job
2
+
3
+ The read side of a queue, as entrypoints an application elevates: list, get, cancel and a socket
4
+ that pushes lifecycle events. Jobs themselves are enqueued and processed through
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:
23
+
24
+ ```typescript
25
+ import { declareJobEntrypoints } from '@owlmeans/server-job'
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:
45
+
46
+ ```typescript
47
+ await context.jobs<ReportJob, ReportResult>(REPORT_QUEUE).create({
48
+ name: 'report:build',
49
+ data: { owner: req.auth!.profileId ?? req.auth!.userId, target: id },
50
+ })
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
+
66
+ ## Ownership
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, {
75
+ queue: REPORT_QUEUE,
76
+ admin: req => req.auth?.scopes?.includes('ops') === true,
77
+ })
78
+ ```
79
+
80
+ ## The one gotcha
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`
94
+
95
+ ## Related
96
+
97
+ - [`@owlmeans/client-job`](../client-job) — the browser half that addresses these entrypoints
98
+ - [`@owlmeans/web-panel`](../web-panel) `./jobs` — `JobProgress`, `JobStatus`, `useJobToasts`
99
+
100
+ <!-- owlmeans:agent-guidance:start -->
101
+ ## Agent guidance
102
+
103
+ This package ships embedded agent skills under `agent-meta/`. After installing your
104
+ `@owlmeans/*` packages, run the OwlMeans agent-skills installer to place them into
105
+ your project's skill store (`.agents/skills/`):
106
+
107
+ ```sh
108
+ npx @owlmeans/agent-skills@^0.1.18-rc.12
109
+ ```
110
+
111
+ The embedded files are version-matched to this package release. Do not edit them
112
+ directly — they are regenerated on each publish. To contribute guidance edits,
113
+ open a PR against the source monorepo.
114
+ <!-- owlmeans:agent-guidance:end -->
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "package": "@owlmeans/server-job",
4
+ "version": "0.1.18-rc.0",
5
+ "generatedAt": "2026-09-04T22:43:25.448Z",
6
+ "canonicalRepo": "https://github.com/owlmeans/common",
7
+ "entries": [
8
+ {
9
+ "kind": "skill",
10
+ "name": "server-job",
11
+ "category": "package-specific",
12
+ "file": "skills/server-job/SKILL.md",
13
+ "canonicalPath": ".agents/skills/server-job/SKILL.md"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: server-job
3
+ description: How to use @owlmeans/server-job — declaring and elevating the list/get/cancel/watch entrypoints over a queue, the ownership rule that scopes them to the authenticated subject, the admin escape hatch, and the socket that pushes JobEvent frames. Auto-invoked when exposing queue jobs to an application's UI or importing job entrypoint helpers.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # @owlmeans/server-job
9
+
10
+ **Layer:** Server
11
+ **Install:** `"@owlmeans/server-job": "^0.1.18-rc.0"` in `dependencies`
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:
38
+
39
+ ```typescript
40
+ import { declareJobEntrypoints } from '@owlmeans/server-job'
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.
52
+
53
+ The guard rides on the base alone and the other four inherit it. It defaults to `DEFAULT_GUARD`
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.
57
+
58
+ `/watch` is declared before `/:id` so the static branch reads first. Declaring a second group is
59
+ the same call with another root.
60
+
61
+ ## Serving
62
+
63
+ ```typescript
64
+ import { serveJobEntrypoints } from '@owlmeans/server-job'
65
+ import { appendRedisQueue } from '@owlmeans/redis-queue'
66
+
67
+ appendRedisQueue(context)
68
+ serveJobEntrypoints(entrypoints, REPORTS, { queue: REPORT_QUEUE })
69
+ context.registerEntrypoints(entrypoints)
70
+ ```
71
+
72
+ `elevate` replaces in place, so an app wanting one handler of its own elevates that alias again
73
+ afterwards. `queue` names which declared queue the group reads; omitted, `ctx.jobs()` answers with
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 },
87
+ })
88
+ ```
89
+
90
+ Reads filter on `data.owner` (`ownerField` renames it) against `auth.profileId ?? auth.userId` —
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.
93
+
94
+ A job that exists but belongs to someone else answers **`UnknownJob`, exactly as an absent one
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:
99
+
100
+ ```typescript
101
+ serveJobEntrypoints(entrypoints, REPORTS, {
102
+ queue: REPORT_QUEUE,
103
+ admin: req => req.auth?.scopes?.includes('ops') === true,
104
+ })
105
+ ```
106
+
107
+ Absent an `admin` check every read is scoped and an anonymous request is refused, so a group wired
108
+ with no options at all is closed rather than open.
109
+
110
+ ## What each handler answers
111
+
112
+ | Alias | Method | Answers |
113
+ |---|---|---|
114
+ | `<root>:list` | GET `/` | `ListResult<JobRecord>`, newest first by `createdAt`. `state`, `name`, `page`, `size` in the query. Paging is opt-in and driven by `size` (1–200): without one the whole scoped list comes back and a `page` alone counts for nothing, since a broker has no default page size. `state` and `name` are plain strings, so a driver state this package does not know still narrows the list |
115
+ | `<root>:get` | GET `/:id` | One `JobRecord`, or `UnknownJob` |
116
+ | `<root>:cancel` | DELETE `/:id` | The record that was removed. Cancellation IS deletion in the queue contract; it does not interrupt a processor mid-run |
117
+ | `<root>:watch` | SOCKET `/watch` | `JobEvent` frames under `job-event`, as the queue publishes them. The subscription is released on the socket's system `close` frame, so a dropped browser stops the queue subscription behind it |
118
+
119
+ ## The one gotcha
120
+
121
+ **A `JobEvent` carries no owner.** `watchJobs` attributes each frame by reading its job back and
122
+ remembers the ids that answered for the life of the connection. A queue configured with
123
+ `removeOnComplete` therefore loses its completion events here — the record they would be
124
+ attributed by is gone by the time the event arrives, and an unattributable frame is dropped rather
125
+ than fanned out to everyone. **Leave completed jobs in place on any queue that is watched.**
126
+
127
+ ## Depends On
128
+
129
+ - `@owlmeans/queue` — `ctx.jobs(queue)`, `JobRecord`, `JobEvent`, `JobState`, `UnknownJob`
130
+ - `@owlmeans/server-api` — `handleRequest`, `handleParams`
131
+ - `@owlmeans/server-socket` — `handleConnection`, and the guard enforcement that fills `req.auth`
132
+ - `@owlmeans/server-entrypoint` — `elevate`
133
+ - `@owlmeans/auth-common` — `DEFAULT_GUARD`
134
+
135
+ ## Related
136
+
137
+ - `queue` — declaring queues, writing processors, and `job.progress()` (there is nothing to watch
138
+ without it)
139
+ - `redis-queue` — the driver, its shutdown rule and its retention options
140
+ - `client-job` — the browser half; `web-panel` — `./jobs` renders what it collects
@@ -0,0 +1,15 @@
1
+ import type { AbstractResponse } from '@owlmeans/entrypoint';
2
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
3
+ import type { JobHandlerOptions } from '../types.js';
4
+ /**
5
+ * Cancel a job and answer with what was cancelled.
6
+ *
7
+ * Cancellation IS deletion in the queue contract — the job and its children leave the broker — so
8
+ * a job already finished cancels to its final record and one already gone answers `UnknownJob`.
9
+ * Nothing here interrupts a processor that is mid-run; a job holding a lock keeps it until the
10
+ * processor notices its own `signal`.
11
+ *
12
+ * @throws {UnknownJob}
13
+ */
14
+ export declare const cancelJob: (opts?: JobHandlerOptions) => RefedEntrypointHandler<AbstractResponse<any>>;
15
+ //# sourceMappingURL=cancel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancel.d.ts","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGpD;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,UACb,iBAAiB,KACvB,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAO3C,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { handleParams } from '@owlmeans/server-api';
2
+ import { jobViewer, jobsOf, readOwnedJob } from '../utils/index.js';
3
+ /**
4
+ * Cancel a job and answer with what was cancelled.
5
+ *
6
+ * Cancellation IS deletion in the queue contract — the job and its children leave the broker — so
7
+ * a job already finished cancels to its final record and one already gone answers `UnknownJob`.
8
+ * Nothing here interrupts a processor that is mid-run; a job holding a lock keeps it until the
9
+ * processor notices its own `signal`.
10
+ *
11
+ * @throws {UnknownJob}
12
+ */
13
+ export const cancelJob = (opts) => handleParams(async ({ id }, ctx, req) => {
14
+ const resource = jobsOf(ctx, opts);
15
+ // Read first: `take` cannot tell whose job it removed, so ownership is settled before it.
16
+ await readOwnedJob(resource, id, await jobViewer(req, ctx, opts), opts);
17
+ return await resource.take(id);
18
+ });
19
+ //# sourceMappingURL=cancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancel.js","sourceRoot":"","sources":["../../src/actions/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAInD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEnE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAwB,EACuB,EAAE,CACjD,YAAY,CAAiB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,0FAA0F;IAC1F,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;IAEvE,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAChC,CAAC,CAAC,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { AbstractResponse } from '@owlmeans/entrypoint';
2
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
3
+ import type { JobHandlerOptions } from '../types.js';
4
+ /**
5
+ * One job.
6
+ *
7
+ * @throws {UnknownJob} for an id that is absent AND for one that belongs to someone else.
8
+ */
9
+ export declare const getJob: (opts?: JobHandlerOptions) => RefedEntrypointHandler<AbstractResponse<any>>;
10
+ //# sourceMappingURL=get.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGpD;;;;GAIG;AACH,eAAO,MAAM,MAAM,UACV,iBAAiB,KACvB,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAK3C,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { handleParams } from '@owlmeans/server-api';
2
+ import { jobViewer, jobsOf, readOwnedJob } from '../utils/index.js';
3
+ /**
4
+ * One job.
5
+ *
6
+ * @throws {UnknownJob} for an id that is absent AND for one that belongs to someone else.
7
+ */
8
+ export const getJob = (opts) => handleParams(async ({ id }, ctx, req) => {
9
+ const resource = jobsOf(ctx, opts);
10
+ return await readOwnedJob(resource, id, await jobViewer(req, ctx, opts), opts);
11
+ });
12
+ //# sourceMappingURL=get.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/actions/get.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAInD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,IAAwB,EACuB,EAAE,CACjD,YAAY,CAAiB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAElC,OAAO,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;AAChF,CAAC,CAAC,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './list.js';
2
+ export * from './get.js';
3
+ export * from './cancel.js';
4
+ export * from './watch.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './list.js';
2
+ export * from './get.js';
3
+ export * from './cancel.js';
4
+ export * from './watch.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,13 @@
1
+ import type { AbstractResponse } from '@owlmeans/entrypoint';
2
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
3
+ import type { JobHandlerOptions } from '../types.js';
4
+ /**
5
+ * The caller's jobs, newest first.
6
+ *
7
+ * Paging is opt-in: a `page` without a `size` is refused by the queue resource rather than
8
+ * silently windowed, because a broker has no default page size to count against. The state and
9
+ * name filters go through the same criteria language as every other resource, so a filter written
10
+ * for this list means the same thing applied to the store the browser holds.
11
+ */
12
+ export declare const listJobs: (opts?: JobHandlerOptions) => RefedEntrypointHandler<AbstractResponse<any>>;
13
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAKzE,OAAO,KAAK,EAAE,iBAAiB,EAAgB,MAAM,aAAa,CAAA;AAGlE;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,UACZ,iBAAiB,KACvB,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAe7C,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { handleRequest } from '@owlmeans/server-api';
2
+ import { DEFAULT_JOB_SORT } from '../consts.js';
3
+ import { jobScope, jobViewer, jobsOf } from '../utils/index.js';
4
+ /**
5
+ * The caller's jobs, newest first.
6
+ *
7
+ * Paging is opt-in: a `page` without a `size` is refused by the queue resource rather than
8
+ * silently windowed, because a broker has no default page size to count against. The state and
9
+ * name filters go through the same criteria language as every other resource, so a filter written
10
+ * for this list means the same thing applied to the store the browser holds.
11
+ */
12
+ export const listJobs = (opts) => handleRequest(async (req, ctx) => {
13
+ const resource = jobsOf(ctx, opts);
14
+ const viewer = await jobViewer(req, ctx, opts);
15
+ const query = (req.query ?? {});
16
+ const where = {
17
+ ...jobScope(viewer, opts),
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' }],
23
+ ...(query.size != null ? { size: query.size, page: query.page ?? 0 } : {}),
24
+ });
25
+ });
26
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/actions/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAMpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,IAAwB,EACuB,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACnF,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAiB,CAAA;IAE/C,MAAM,KAAK,GAAwB;QACjC,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAA;IAED,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE;QAChC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAClD,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;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,17 @@
1
+ import type { AbstractResponse } from '@owlmeans/entrypoint';
2
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
3
+ import type { JobHandlerOptions } from '../types.js';
4
+ /**
5
+ * Push this caller's job lifecycle events down a socket.
6
+ *
7
+ * The frames are `JobEvent`s exactly as the queue publishes them, under the {@link JOB_EVENT}
8
+ * event name — no shape of this package's own, so a client applies them with the contract types.
9
+ *
10
+ * **A `JobEvent` carries no owner**, so each one is attributed by reading its job back, and the
11
+ * ids that answered are remembered for the life of the connection. A queue configured with
12
+ * `removeOnComplete` therefore loses its completion events here: the record they would be
13
+ * attributed by is gone by the time the event arrives, and an unattributable event is dropped
14
+ * rather than fanned out to everyone. Leave completed jobs in place on any queue that is watched.
15
+ */
16
+ export declare const watchJobs: (opts?: JobHandlerOptions) => RefedEntrypointHandler<AbstractResponse<any>>;
17
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/actions/watch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAKzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,UACb,iBAAiB,KACvB,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAyC7C,CAAA"}
@@ -0,0 +1,57 @@
1
+ import { handleConnection } from '@owlmeans/server-socket';
2
+ import { MessageType } from '@owlmeans/socket';
3
+ import { JOB_EVENT } from '../consts.js';
4
+ import { jobViewer, jobsOf, owns } from '../utils/index.js';
5
+ /**
6
+ * Push this caller's job lifecycle events down a socket.
7
+ *
8
+ * The frames are `JobEvent`s exactly as the queue publishes them, under the {@link JOB_EVENT}
9
+ * event name — no shape of this package's own, so a client applies them with the contract types.
10
+ *
11
+ * **A `JobEvent` carries no owner**, so each one is attributed by reading its job back, and the
12
+ * ids that answered are remembered for the life of the connection. A queue configured with
13
+ * `removeOnComplete` therefore loses its completion events here: the record they would be
14
+ * attributed by is gone by the time the event arrives, and an unattributable event is dropped
15
+ * rather than fanned out to everyone. Leave completed jobs in place on any queue that is watched.
16
+ */
17
+ export const watchJobs = (opts) => handleConnection(async (conn, ctx, req) => {
18
+ const resource = jobsOf(ctx, opts);
19
+ const viewer = await jobViewer(req, ctx, opts);
20
+ const mine = new Set();
21
+ const attributable = async (event) => {
22
+ if (viewer == null || mine.has(event.id)) {
23
+ return true;
24
+ }
25
+ const record = await resource.load(event.id);
26
+ if (record == null || !owns(record, viewer, opts)) {
27
+ return false;
28
+ }
29
+ mine.add(event.id);
30
+ return true;
31
+ };
32
+ const unsubscribe = await resource.subscribe(async (event) => {
33
+ try {
34
+ if (await attributable(event)) {
35
+ await conn.notify(JOB_EVENT, event);
36
+ }
37
+ }
38
+ catch (e) {
39
+ console.error('Job watch notify error:', e);
40
+ }
41
+ });
42
+ conn.listen(async (message) => {
43
+ if (typeof message !== 'object') {
44
+ return;
45
+ }
46
+ const msg = message;
47
+ if (msg.type === MessageType.System && msg.event === 'close') {
48
+ try {
49
+ await unsubscribe();
50
+ }
51
+ catch (e) {
52
+ console.error('Job watch unsubscribe error:', e);
53
+ }
54
+ }
55
+ });
56
+ });
57
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/actions/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAI1D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAE3D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAwB,EACuB,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IAC5F,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;IAC9C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAE9B,MAAM,YAAY,GAAG,KAAK,EAAE,KAAe,EAAoB,EAAE;QAC/D,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAC5C,IAAI,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;YAClD,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAElB,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE;QACzD,IAAI,CAAC;YACH,IAAI,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YACrC,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,MAAM,CAAC,KAAK,EAAC,OAAO,EAAC,EAAE;QAC1B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAM;QACR,CAAC;QACD,MAAM,GAAG,GAAG,OAA6B,CAAA;QACzC,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC7D,IAAI,CAAC;gBACH,MAAM,WAAW,EAAE,CAAA;YACrB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAA;YAClD,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The alias root a target app gets when its declaration names none, and the path segment the
3
+ * group answers under.
4
+ */
5
+ export declare const DEFAULT_JOB_ROOT = "jobs";
6
+ export declare const DEFAULT_JOB_PATH = "/jobs";
7
+ /**
8
+ * Where the owner is recorded inside a job's payload.
9
+ *
10
+ * A `JobRecord` has no owner column — the broker keeps only what the contract declares — so the
11
+ * producer writes the subject into the job's own `data`, and every read here filters on
12
+ * `data.<field>`. Change it per declaration when the app's payloads already name the subject
13
+ * something else.
14
+ */
15
+ export declare const DEFAULT_OWNER_FIELD = "owner";
16
+ /**
17
+ * The socket event a `JobEvent` frame is pushed under.
18
+ *
19
+ * This name, and the `<root>:<verb>` alias shape in `entrypoints.ts`, are the whole contract
20
+ * between this package and `@owlmeans/client-job` — the two halves cannot share a module without
21
+ * dragging fastify into a browser bundle, so they each state it and the skills pin it.
22
+ */
23
+ export declare const JOB_EVENT = "job-event";
24
+ /** Newest first: a job list is read to see what is happening now. */
25
+ export declare const DEFAULT_JOB_SORT = "createdAt";
26
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,SAAS,CAAA;AACtC,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,UAAU,CAAA;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,cAAc,CAAA;AAEpC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,cAAc,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The alias root a target app gets when its declaration names none, and the path segment the
3
+ * group answers under.
4
+ */
5
+ export const DEFAULT_JOB_ROOT = 'jobs';
6
+ export const DEFAULT_JOB_PATH = '/jobs';
7
+ /**
8
+ * Where the owner is recorded inside a job's payload.
9
+ *
10
+ * A `JobRecord` has no owner column — the broker keeps only what the contract declares — so the
11
+ * producer writes the subject into the job's own `data`, and every read here filters on
12
+ * `data.<field>`. Change it per declaration when the app's payloads already name the subject
13
+ * something else.
14
+ */
15
+ export const DEFAULT_OWNER_FIELD = 'owner';
16
+ /**
17
+ * The socket event a `JobEvent` frame is pushed under.
18
+ *
19
+ * This name, and the `<root>:<verb>` alias shape in `entrypoints.ts`, are the whole contract
20
+ * between this package and `@owlmeans/client-job` — the two halves cannot share a module without
21
+ * dragging fastify into a browser bundle, so they each state it and the skills pin it.
22
+ */
23
+ export const JOB_EVENT = 'job-event';
24
+ /** Newest first: a job list is read to see what is happening now. */
25
+ export const DEFAULT_JOB_SORT = 'createdAt';
26
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAA;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAA;AAE1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAA;AAEpC,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG,WAAW,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { CommonEntrypoint } from '@owlmeans/entrypoint';
2
+ import type { JobEntrypointAliases, JobEntrypointOptions } from './types.js';
3
+ /**
4
+ * The aliases one job group answers under.
5
+ *
6
+ * The shape — `<root>` and `<root>:<verb>` — is the contract `@owlmeans/client-job` addresses the
7
+ * same group by, so a group renamed here is renamed there by passing the same root.
8
+ */
9
+ export declare const jobEntrypointAliases: (root: string) => JobEntrypointAliases;
10
+ /**
11
+ * Declare the list/get/cancel/watch entrypoints of one job group.
12
+ *
13
+ * It belongs in the SHARED package of a target app — the one both the API and the browser import —
14
+ * so that the server elevates and the client calls the very declarations, and neither side ever
15
+ * writes a path. Declaring a second group is the same call with another root.
16
+ *
17
+ * The guard rides on the base alone: guards are inherited, so stating it once is what keeps the
18
+ * four from drifting apart.
19
+ */
20
+ export declare const declareJobEntrypoints: (root: string, opts?: JobEntrypointOptions) => CommonEntrypoint[];
21
+ //# sourceMappingURL=entrypoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAM5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,SAAU,MAAM,KAAG,oBAMlD,CAAA;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,SAC1B,MAAM,SAAS,oBAAoB,KACxC,gBAAgB,EAoBlB,CAAA"}