@worker-protocol/hono 0.1.0 → 0.1.1

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 (2) hide show
  1. package/README.md +155 -0
  2. package/package.json +3 -3
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @worker-protocol/hono
2
+
3
+ The protocol's surface as Hono routes, and `mount()`: implement an interface, and get every address,
4
+ header, envelope and refusal worker-protocol fixes.
5
+
6
+ **worker-protocol is an open specification for Workers that can be seen, operated and given work by
7
+ people who did not build them.** HTTP and JSON Schema, no runtime. A *Worker* describes itself in a
8
+ Descriptor served at `/.well-known/worker-protocol` — the one address this protocol fixes — and
9
+ declares there which of its Capabilities it implements: `health`, `metrics`, `actions`, `alerts`,
10
+ `activity`, `nudges`, `tasks`, `events`. Any combination is allowed, including none.
11
+
12
+ This package is what makes complying cheap. You write what your Worker *is* — what it knows how to
13
+ do, what it counts, which conditions hold, what it is working on — and `mount()` writes the rest,
14
+ once, the same way in every Worker.
15
+
16
+ ## Install
17
+
18
+ ```
19
+ npm i @worker-protocol/hono hono zod
20
+ ```
21
+
22
+ `hono` (`^4.13.7`) and `zod` (`^4.5.4`) are peer dependencies, so your app and this package share
23
+ one instance of each.
24
+
25
+ ## A Worker
26
+
27
+ ```ts
28
+ import { action, defineWorker, memoryOutcomes, mount, type OpenTask } from "@worker-protocol/hono";
29
+ import * as z from "zod";
30
+
31
+ const SILENT_VEHICLE = "tech.rowing.fleet.check-silent-vehicle";
32
+
33
+ const silent = new Map<string, Date>();
34
+ const checked = new Set<string>();
35
+
36
+ // Where a repeat under one idempotency key finds the outcome the first call recorded. Built out
37
+ // here, not inside the Worker below: that is answered per request, and a store built there would
38
+ // forget what the last one recorded while the caller believed the repeat was protected.
39
+ const outcomes = memoryOutcomes();
40
+
41
+ export const fleetWorker = defineWorker<{ CREDENTIAL?: string }>((env) => ({
42
+ // Not the URL, and not derived from it: moving this Worker to another host must not make it
43
+ // another Worker.
44
+ id: "tech.rowing.fleet.watcher",
45
+
46
+ authenticate: (token) => (token === env.CREDENTIAL ? "accepted" : "unauthenticated"),
47
+
48
+ health: () => ({
49
+ status: silent.size > 100 ? "degraded" : "healthy",
50
+ checks: { store: { status: "healthy", detail: `${silent.size} vehicles tracked` } },
51
+ }),
52
+
53
+ actions: {
54
+ outcomes,
55
+ accepts: {
56
+ "answer-check": action({
57
+ input: z.object({ vehicle: z.string().min(1), reachable: z.boolean() }),
58
+ result: z.object({ recordedAt: z.string() }),
59
+ // A repeat under the caller's key replays the first answer instead of doing the work
60
+ // again, against the store named above.
61
+ idempotency: { required: true, from: "header", windowSeconds: 3600 },
62
+ run: ({ vehicle }) => {
63
+ checked.add(vehicle);
64
+ return { recordedAt: new Date().toISOString() };
65
+ },
66
+ }),
67
+ },
68
+ },
69
+
70
+ tasks: {
71
+ // What this Worker sends with a Task of this type, and the one operation of its own that
72
+ // answers it. Whoever does the work never has to be told where to send the answer.
73
+ raises: {
74
+ [SILENT_VEHICLE]: {
75
+ payload: z.object({ vehicle: z.string() }),
76
+ answeredBy: "answer-check",
77
+ },
78
+ },
79
+ // The condition, and the whole of what this Worker owes. A Task exists while its vehicle is
80
+ // quiet and unchecked, and it stops existing when that stops being true: nobody closes one,
81
+ // so nothing here can be left open by a consumer that crashed.
82
+ current: (): OpenTask[] =>
83
+ [...silent.entries()]
84
+ .filter(([vehicle]) => !checked.has(vehicle))
85
+ .map(([vehicle, since]) => ({ id: `silent:${vehicle}`, type: SILENT_VEHICLE, payload: { vehicle }, since })),
86
+ },
87
+ }));
88
+
89
+ export const app = mount(fleetWorker);
90
+ ```
91
+
92
+ `app` is an `OpenAPIHono`, so `app.fetch` is what every platform wants: `export default { fetch:
93
+ app.fetch }` on Cloudflare, Vercel edge and Deno Deploy; `serve({ fetch: app.fetch })` on Node, Bun
94
+ and Deno. Writing the Worker as a function of its environment is what makes the same file run in all
95
+ of them — bindings and secrets arrive per request on an edge platform and are ambient on a server,
96
+ and a Worker that reads them at module scope works in one place only.
97
+
98
+ ## What `mount()` carries, so your Worker does not
99
+
100
+ - Every address, derived from the Capabilities you declared, and the Descriptor that publishes them.
101
+ - The Descriptor itself, including the JSON Schema of each Action's input and each Task type's
102
+ payload, written from the Zod objects you handed in — so what a console renders a form from and
103
+ what your Worker accepts cannot drift apart.
104
+ - The error envelope and which code answers which refusal, the difference between a `reject` a
105
+ caller must not repeat and a `retry` it should.
106
+ - The page envelope, its cursor and the order a page is returned in.
107
+ - Metric bucket boundaries, cut in the time zone your Worker declared.
108
+ - The idempotency window: a repeat under a key replays the recorded outcome instead of performing
109
+ the work twice.
110
+ - Authentication, as `Authorization: Bearer <token>` on every address, answered by your
111
+ `authenticate`; and the refusal of a Nudge for a Task type you declare no Skill for.
112
+
113
+ What it never does is anything the specification leaves to you: what a Task's condition is, what an
114
+ Action does, what a number means.
115
+
116
+ ## Also exported
117
+
118
+ `action()` and `jsonSchema()` for declaring an Action; `memoryOutcomes()` as an outcome store for a
119
+ single long-lived process (an edge deployment wants a durable one — a `Map` per isolate has the same
120
+ problem one step further out); `CODES` and `ErrorCode`; `bucketsIn`, `startOf`, `endOf`, `rfc3339`
121
+ for metric boundaries; and the route objects themselves — `readDescriptor`, `pollHealth`,
122
+ `readMetric`, `performAction`, `readTasks`, `readAlerts`, `readActivity` — which are the declaration
123
+ the protocol's OpenAPI documents are generated from.
124
+
125
+ Types: `Worker`, `Activity`, `Alert`, `Answer`, `Refusal`, `SkillDeclaration`, `OpenTask`,
126
+ `TaskFacts`, `TaskTypes`, `MetricFacts`, `MetricQuery`, `MetricSample`, `Bucket`, `ActionFacts`,
127
+ `ActionDeclarations`, `OutcomeStore`, `Reservation`, `Recorded`, `WorkerSource`, `WorkerBuilder`,
128
+ `ExecutionCtx`.
129
+
130
+ ## What standing this package has
131
+
132
+ **Nothing in it carries behavior of its *own*.** What is forbidden is a package doing something the
133
+ specification does not say — that is how a package becomes the standard and the text starts to rot.
134
+ What is wanted is a package carrying everything the specification *does* say, because the
135
+ alternative is every Worker author deriving the same rules again and the ones who get a detail wrong
136
+ being non-conformant in a way only a verifier ever finds. Every line here cites the rule id it
137
+ carries. If `mount()` does something no rule requires, that is the bug.
138
+
139
+ What vouches for it is not this README: it is `@worker-protocol/conformance` run against a Worker
140
+ built on it.
141
+
142
+ ## Related packages
143
+
144
+ - `@worker-protocol/schemas` — the Zod objects that generate the normative JSON Schemas.
145
+ - `@worker-protocol/client` — `consume()`: read a Worker, and take work from it. A consumer is not a
146
+ server and installs no web framework.
147
+ - `@worker-protocol/conformance` — point it at a Worker's base URL, get a report of what it complies
148
+ with.
149
+
150
+ ## License and name
151
+
152
+ Apache-2.0, patent grant included — implement the protocol in any product, commercial or not,
153
+ without asking anyone. The name is not part of that grant (Apache-2.0 §6): a claim that something
154
+ *speaks worker-protocol* is one this project vouches for, and the conformance tool is how it is
155
+ earned.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worker-protocol/hono",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "workerProtocolEdition": "0.1",
5
5
  "description": "The protocol's surface as Hono routes, and mount(): a Worker author implements an interface and gets every address, header and refusal this protocol fixes",
6
6
  "license": "Apache-2.0",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/rowing-tech/worker-protocol.git",
10
10
  "directory": "packages/hono"
11
11
  },
12
- "homepage": "https://github.com/rowing-tech/worker-protocol#readme",
12
+ "homepage": "https://github.com/rowing-tech/worker-protocol/tree/main/packages/hono#readme",
13
13
  "bugs": "https://github.com/rowing-tech/worker-protocol/issues",
14
14
  "publishConfig": {
15
15
  "access": "public"
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@hono/zod-openapi": "1.6.3",
33
- "@worker-protocol/schemas": "0.1.0"
33
+ "@worker-protocol/schemas": "0.1.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "hono": "^4.13.7",