@plurnk/plurnk-schemes-http 2.0.6 → 2.0.7
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 +7 -7
- package/SPEC.md +10 -10
- package/dist/Ws.js +8 -8
- package/docs/https.md +37 -44
- package/docs/wss.md +27 -32
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ authored against the DB-free
|
|
|
10
10
|
|
|
11
11
|
| Operation | Behavior |
|
|
12
12
|
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
13
|
+
| `await plurnk.read({ path: "http(s)://…" })` | Fetch or reuse/revalidate a GET representation, then project the selected response channel |
|
|
14
|
+
| `await plurnk.read({ path: "http(s)://…", range: "…" })` | Apply standard entry scope after the same acquisition/revalidation; scope does not bypass it |
|
|
15
|
+
| `await plurnk.find({ path: "http(s)://…", pattern: "…" })` | Materialize an exact URL when required, then use the universal entry query and matcher |
|
|
16
|
+
| `await plurnk.send({ target: "http(s)://…", text: "…" })` | POST the body and stream the response |
|
|
17
|
+
| `await plurnk.edit({ path: "http(s)://…", content: "…" })` | PUT a whole-resource replacement; line-scoped HTTP edits are invalid |
|
|
18
|
+
| `await plurnk.kill({ path: "http(s)://…" })` | Cancel a live acquisition of the address, or forget its stored response |
|
|
19
|
+
| `await plurnk.kill({ path: "http(s)://…", metadata: [{"remote": true}] })` | DELETE the remote resource and stream the response |
|
|
20
20
|
|
|
21
21
|
A path-pattern FIND surveys already-materialized web entries; it does not crawl
|
|
22
22
|
or discover the remote web. Exact matcher FIND shares the standard flat
|
package/SPEC.md
CHANGED
|
@@ -16,13 +16,13 @@ Identity and transport URLs normalize through `NetworkAddress` ({§network-addre
|
|
|
16
16
|
|
|
17
17
|
| Operation | Remote action | Contract |
|
|
18
18
|
|---|---|---|
|
|
19
|
-
| Exact `
|
|
20
|
-
| Exact `
|
|
21
|
-
| Pattern `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
19
|
+
| Exact `plurnk.read({ path: url, range? })` | `GET` (unless a fresh cached `GET` is usable) | Scope-blind `prepareRepresentation`, then Core selects channel and scope |
|
|
20
|
+
| Exact `plurnk.find({ path: url })` | `GET` only when acquisition is required | Same preparation, then universal query, matcher, weighting, and pagination |
|
|
21
|
+
| Pattern `plurnk.find({ path: patternUrl })` | None (`PathSyntax.hasGlob`, {§path-syntax}) | Query already-materialized web entries in the workspace without remote discovery |
|
|
22
|
+
| `plurnk.send({ target: url, text })` | `POST` | Proposes (`effect: "host"`, `202`); on apply, streams and persists response under `url` |
|
|
23
|
+
| `plurnk.edit({ path: url, content })` | `PUT` | Proposes (`effect: "host"`, `202`); replaces whole remote resource (line markers fail `400`: `Remove the line range and submit the complete replacement body.`) |
|
|
24
|
+
| `plurnk.kill({ path: url })` | None | Cancels all live workspace acquisitions of `url` (owners settle `499`), else deletes local stored entry |
|
|
25
|
+
| `plurnk.kill({ path: url, metadata: ["{\"remote\": true}"] })` | `DELETE` | Proposes (`effect: "host"`, `202`); remaining options in the metadata block become request headers |
|
|
26
26
|
|
|
27
27
|
- **Headers and proposals (`http-outbound-proposes`):** Request headers are passed in one `[{"Key": "value"}]` metadata block ({§slot-order}). `POST`, `PUT`, remote `DELETE`, and WebSocket `EDIT`/`SEND` frames declare `effect: "host"` and return `202` ({§proposal}, {§exec-lifetime}), governed by `PLURNK_SERVICE_EFFECT_HOST`. `GET` and WebSocket `READ` connection opens are observational and ungated; a WebSocket frame checks for an open socket both before proposing and again at `applyResolution`, and the resolver may replace the proposed body before it is sent.
|
|
28
28
|
|
|
@@ -150,7 +150,7 @@ stateDiagram-v2
|
|
|
150
150
|
|
|
151
151
|
| Operation / event | Contract |
|
|
152
152
|
|---|---|
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
153
|
+
| `plurnk.read({ path: "ws(s)://..." })` | Claims workspace/URL coordinate, seeds entry and `StreamSubscription`, opens native `WebSocket`, and returns `102` on `open`. Concurrent duplicate `READ`s join the pending open and return `200` with connection state without opening a second socket. |
|
|
154
|
+
| `plurnk.edit({ path: "ws(s)://...", content })` / `plurnk.send({ target: "ws(s)://...", text })` | Single-statement markerless `EDIT` and `SEND` (`200`) propose (`202`, `effect: "host"`) and send one text frame when the owner is `open` (`readyState === OPEN`). Absent/non-open owner fails `409` (`READ the WebSocket URL before sending a message.`); send throw fails `502` (`The WebSocket message could not be sent.`); line ranges and multi-statement batches fail `400`/`409`. |
|
|
155
|
+
| `plurnk.kill({ path: "ws(s)://..." })` | Closes/cancels the claimed owner (`404` when no live owner exists; close throw fails `502`: `The WebSocket connection could not be closed.`). |
|
|
156
156
|
| Inbound frames & settlement | Text frames chain sequentially onto `messages` after `open`. A binary frame retains the text prefix, prunes later frames, closes with WebSocket code `4003`, and settles `415 binary-frame-unsupported`. First persistence failure prunes later frames and settles `500 message-persistence-failed`. Pre-open close settles `502 connection-failed`; post-open close drains accepted frames and settles `200` ({§execution-output-identity}, {§scheme-operation-dispatch}). |
|
package/dist/Ws.js
CHANGED
|
@@ -367,28 +367,28 @@ export default class Ws {
|
|
|
367
367
|
retryable: false,
|
|
368
368
|
});
|
|
369
369
|
}
|
|
370
|
-
const open = this.#openFor(statement.target, ctx, "
|
|
370
|
+
const open = this.#openFor(statement.target, ctx, "edit");
|
|
371
371
|
if (!open.ok)
|
|
372
372
|
return open.result;
|
|
373
|
-
return Ws.#propose("
|
|
373
|
+
return Ws.#propose("edit", statement.target, statement.body ?? "");
|
|
374
374
|
}
|
|
375
|
-
// {§op-shapes} —
|
|
375
|
+
// {§op-shapes} — send is messaging; a kill closes the owner.
|
|
376
376
|
async send(statement, ctx) {
|
|
377
377
|
if (statement.metadata !== null)
|
|
378
378
|
return Ws.#metadataUnsupported();
|
|
379
379
|
if (statement.target === null || statement.target.kind !== "url") {
|
|
380
|
-
return Ws.#bad(400, "bad-target", "
|
|
380
|
+
return Ws.#bad(400, "bad-target", "send requires a ws(s):// URL target.", {
|
|
381
381
|
stage: "target-validation",
|
|
382
382
|
recovery: "Provide a ws(s):// URL target.",
|
|
383
383
|
retryable: false,
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
|
-
const open = this.#openFor(statement.target, ctx, "
|
|
386
|
+
const open = this.#openFor(statement.target, ctx, "send");
|
|
387
387
|
if (!open.ok)
|
|
388
388
|
return open.result;
|
|
389
|
-
return Ws.#propose("
|
|
389
|
+
return Ws.#propose("send", statement.target, statement.body?.raw ?? "");
|
|
390
390
|
}
|
|
391
|
-
// {§http-manifest} — a frame is data leaving the process, so
|
|
391
|
+
// {§http-manifest} — a frame is data leaving the process, so edit and send propose
|
|
392
392
|
// and write on the settlement. The socket is live, so this admission runs twice: once before
|
|
393
393
|
// the proposal, so nobody is asked to approve a frame on a socket that is not open, and again
|
|
394
394
|
// at apply, because it may have closed while the question was open.
|
|
@@ -410,7 +410,7 @@ export default class Ws {
|
|
|
410
410
|
if (url.kind !== "url")
|
|
411
411
|
throw new Error("A WebSocket proposal's target is a URL.");
|
|
412
412
|
// This scheme proposes exactly two operations; a settlement never widens that set.
|
|
413
|
-
if (operation !== "
|
|
413
|
+
if (operation !== "edit" && operation !== "send") {
|
|
414
414
|
throw new Error(`A WebSocket proposal named an operation this scheme never proposes: ${String(operation)}.`);
|
|
415
415
|
}
|
|
416
416
|
const body = request.body ?? (typeof attrs.body === "string" ? attrs.body : "");
|
package/docs/https.md
CHANGED
|
@@ -6,56 +6,56 @@ Plain `http://` is accepted as well, for an endpoint that requires it.
|
|
|
6
6
|
|
|
7
7
|
Read and modify web resources through addressable HTTP(S) entries.
|
|
8
8
|
|
|
9
|
-
A web URL is an addressable entry. Every exact
|
|
9
|
+
A web URL is an addressable entry. Every exact `plurnk.read` acquires or refreshes a
|
|
10
10
|
complete representation when needed, then core selects the channel and applies
|
|
11
11
|
the requested text/byte scope. An HTML page's `body` is the server source; its
|
|
12
12
|
readable Markdown (a materializer's or the local reader's) is `#readable`, read as
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
| Operation
|
|
16
|
-
|
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
|
|
24
|
-
A path-pattern
|
|
25
|
-
workspace; a pattern cannot discover the remote web. For matched content,
|
|
13
|
+
`await plurnk.read({ path: "https://…#readable" });`, and every `read` names the page's other channels with their tokens.
|
|
14
|
+
|
|
15
|
+
| Operation | Remote action | Effect |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `await plurnk.read({ path: "https://…", range })` | GET if needed | Acquire/reuse the complete response, then return the selected scoped text |
|
|
18
|
+
| `await plurnk.find({ path: "https://…", pattern })` | GET if needed | Return matching text regions for a scoped `read` |
|
|
19
|
+
| `await plurnk.send({ target: "https://…", text })` | POST | Submit the body and stream the response |
|
|
20
|
+
| `await plurnk.edit({ path: "https://…", content })` | PUT | Replace the whole remote resource; a line scope does not apply |
|
|
21
|
+
| `await plurnk.kill({ path: "https://…" })` | none | Cancel a live acquisition of the address, or forget its stored response |
|
|
22
|
+
| `await plurnk.kill({ path: "https://…", metadata: ["{\"remote\": true}"] })` | DELETE | Delete the remote resource and stream the response |
|
|
23
|
+
|
|
24
|
+
A path-pattern `find` searches only web entries already materialized in the
|
|
25
|
+
workspace; a pattern cannot discover the remote web. For matched content, `read`
|
|
26
26
|
the returned `#channel` on the path (`…#channel`) using the `region` as
|
|
27
|
-
|
|
27
|
+
`range: "startLine,startColumn,endLine,endColumn"`. Caller cancellation of an exact
|
|
28
28
|
acquisition returns `499 cancelled`.
|
|
29
29
|
|
|
30
30
|
## Channels
|
|
31
31
|
|
|
32
|
-
| Response
|
|
33
|
-
|
|
|
34
|
-
| Negotiated origin Markdown
|
|
35
|
-
| GET HTML
|
|
36
|
-
| GET `text/event-stream`
|
|
37
|
-
| Configured textual response
|
|
38
|
-
| Origin HTTP `4xx`/`5xx`
|
|
39
|
-
| Binary with a readable projection
|
|
40
|
-
| Binary without a readable projection | Original bytes, shown as hex
|
|
32
|
+
| Response | `body` | Other channel |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| Negotiated origin Markdown | Exact origin Markdown | Status and headers in `#header`; nothing else |
|
|
35
|
+
| GET HTML | Original server HTML | Materializer Markdown or local HTML-reader projection in `#readable` |
|
|
36
|
+
| GET `text/event-stream` | Event `data` chunks after `read` `102` | Initial response in `#header` |
|
|
37
|
+
| Configured textual response | Complete Fetch-decoded text under its declared type | Status and headers in `#header` |
|
|
38
|
+
| Origin HTTP `4xx`/`5xx` | Preserve available origin text or bytes | Exact status on each origin-backed channel |
|
|
39
|
+
| Binary with a readable projection | Original bytes, shown as hex | Facts/text in `#readable`; evidence in `#header` |
|
|
40
|
+
| Binary without a readable projection | Original bytes, shown as hex | Evidence in `#header`; no invented facts |
|
|
41
41
|
|
|
42
42
|
`#header` holds origin and acquisition evidence, including a materializer's
|
|
43
43
|
route, status and timing. `body`, `#header` and `#readable` carry independent
|
|
44
44
|
outcomes: `#readable` or `#header` can remain readable after a source failure,
|
|
45
45
|
and a direct non-success origin response is still materialized, its
|
|
46
46
|
origin-backed channels carrying the exact `http-response-status` Problem. A
|
|
47
|
-
|
|
47
|
+
`read` of the source names `#readable` and its tokens. `422
|
|
48
48
|
no-readable-projection` on `#readable` means the local route produced no
|
|
49
49
|
readable text; `413 projection-input-limit` is input above the configured byte
|
|
50
50
|
ceiling; `404 channel-not-found` lists the available channels and does not
|
|
51
51
|
mean the URL is missing. A binary response keeps its original bytes in `body`;
|
|
52
|
-
|
|
52
|
+
`read` shows their hex and, on a supporting model, attaches the native media;
|
|
53
53
|
`#bytes` selects the hex view explicitly. A failed mutation's response evidence
|
|
54
54
|
is in `#header`, and a retry re-executes the mutation.
|
|
55
55
|
|
|
56
|
-
For SSE,
|
|
56
|
+
For SSE, `read` returns `102` while events continue; origin close settles the
|
|
57
57
|
subscription at `200`, and later cancellation or transfer failure settles it
|
|
58
|
-
at `499` or `502` without rewriting the initial
|
|
58
|
+
at `499` or `502` without rewriting the initial `read`. Re-reading an exact URL
|
|
59
59
|
reuses a complete GET while the operator TTL and the origin's own lifetime
|
|
60
60
|
allow; request metadata, a `Vary` response, a partial `206`, or a POST, PUT or
|
|
61
61
|
DELETE response is never reused as a later GET. Scope never suppresses
|
|
@@ -63,20 +63,13 @@ acquisition or refresh.
|
|
|
63
63
|
|
|
64
64
|
## Request headers
|
|
65
65
|
|
|
66
|
-
Request headers share one `
|
|
67
|
-
target and any scope; option objects in the array merge left to right, and
|
|
68
|
-
metadata stays on one line.
|
|
66
|
+
Request headers share one `metadata` array of JSON option objects; option objects in the array merge left to right.
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
```
|
|
68
|
+
await plurnk.read({ path: "https://api.example.com/v1/me", metadata: ["{\"Accept\": \"application/json\"}"] });
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
{"done":true}
|
|
75
|
-
```
|
|
70
|
+
await plurnk.edit({ path: "https://api.example.com/v1/thing/42", metadata: ["{\"Content-Type\": \"application/json\"}"], content: "{\"done\":true}" });
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
{"query":"plurnk"}
|
|
79
|
-
```
|
|
72
|
+
await plurnk.send({ target: "https://api.example.com/v1/search", metadata: ["{\"Content-Type\": \"application/json\"}"], text: "{\"query\":\"plurnk\"}" });
|
|
80
73
|
|
|
81
74
|
A request with explicit metadata is ineligible for later cache reuse, and its
|
|
82
75
|
HTML uses the local reader projection: request headers never reach the
|
|
@@ -84,10 +77,10 @@ materializer. GET acquisition of a GitHub `…/blob/…` URL uses its
|
|
|
84
77
|
`raw.githubusercontent.com` source; the addressed URL remains entry identity,
|
|
85
78
|
and POST, PUT and DELETE never use that rewrite.
|
|
86
79
|
|
|
87
|
-
A `
|
|
88
|
-
`[{"remote": true}]`: while an acquisition is in flight it cancels that
|
|
89
|
-
acquisition, otherwise it forgets the stored response so the next
|
|
90
|
-
acquire it again. With `
|
|
80
|
+
A `plurnk.kill` of an `https://` address never reaches the remote unless it carries
|
|
81
|
+
`metadata: ["{\"remote\": true}"]`: while an acquisition is in flight it cancels that
|
|
82
|
+
acquisition, otherwise it forgets the stored response so the next `read` must
|
|
83
|
+
acquire it again. With `{"remote": true}`, the other options in that object
|
|
91
84
|
are the DELETE request's headers.
|
|
92
85
|
|
|
93
|
-
For a persistent bidirectional connection, `wss://` (`plurnk
|
|
86
|
+
For a persistent bidirectional connection, `wss://` (`skill://plurnk/wss.md`).
|
package/docs/wss.md
CHANGED
|
@@ -5,45 +5,40 @@
|
|
|
5
5
|
Maintain persistent, bidirectional WebSocket connections as addressable entries.
|
|
6
6
|
|
|
7
7
|
Use WebSocket for a persistent, bidirectional connection. `wss` is a
|
|
8
|
-
stateful scheme, not an HTTP content type:
|
|
9
|
-
while
|
|
10
|
-
|
|
11
|
-
| Operation
|
|
12
|
-
|
|
|
13
|
-
| `
|
|
14
|
-
| A second
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
|
|
19
|
-
| Connection state | Meaning
|
|
20
|
-
|
|
|
21
|
-
| `claimed`
|
|
22
|
-
| `connecting` | Native socket exists but has not emitted `open`
|
|
23
|
-
| `open`
|
|
24
|
-
| `settling`
|
|
8
|
+
stateful scheme, not an HTTP content type: `plurnk.read` opens a workspace connection,
|
|
9
|
+
while `plurnk.edit`, `plurnk.send`, and `plurnk.kill` address that connection.
|
|
10
|
+
|
|
11
|
+
| Operation | Effect |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `await plurnk.read({ path: "wss://host/path" })` | Claim the address, connect, mark `messages` active on `open`, and stream inbound frames |
|
|
14
|
+
| A second `read` of the same address | Read the retained representation; reuse the existing connection without reconnecting |
|
|
15
|
+
| `await plurnk.edit({ path: "wss://host/path", content })` | Propose one whole text frame through an already-open connection, sent on acceptance; ranges and batches are invalid |
|
|
16
|
+
| `await plurnk.send({ target: "wss://host/path", text })` | Propose one whole text frame, sent on acceptance; it may follow the opening `read` in the same turn |
|
|
17
|
+
| `await plurnk.kill({ path: "wss://host/path" })` | Close or cancel the connection; an address with no connection is `404` |
|
|
18
|
+
|
|
19
|
+
| Connection state | Meaning | `edit` or directed `send` |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `claimed` | Address reserved while entry/subscription setup runs | `409`; no second connection is created |
|
|
22
|
+
| `connecting` | Native socket exists but has not emitted `open` | `409`; wait for the active stream event |
|
|
23
|
+
| `open` | `open` was observed and the native state is open | Proposes one whole text frame, sent on acceptance |
|
|
24
|
+
| `settling` | A terminal transition owns cleanup | `409`; wait for cleanup before another `read` |
|
|
25
25
|
|
|
26
26
|
The native `open` event plus durable `messages` activation is the acquisition
|
|
27
|
-
boundary. It emits the ordinary metadata-only stream event and returns the
|
|
28
|
-
at `102`; any session or client in the workspace may then
|
|
29
|
-
or
|
|
27
|
+
boundary. It emits the ordinary metadata-only stream event and returns the `read`
|
|
28
|
+
at `102`; any session or client in the workspace may then `edit`, `send`,
|
|
29
|
+
or `kill` the connection. A close before acquisition is a direct `502` connection
|
|
30
30
|
failure. After acquisition, close, cancellation, and failure settle the retained
|
|
31
|
-
subscription without rewriting the initial
|
|
31
|
+
subscription without rewriting the initial `read`.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
the opening
|
|
33
|
+
`edit` and directed `send` share the same outbound-frame behavior. Both can follow
|
|
34
|
+
the opening `read` in one turn: operations execute in authored order. Only the
|
|
35
35
|
turn's disposition operation is deferred until the other operations have run.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
```
|
|
37
|
+
await plurnk.read({ path: "wss://api.example.com/feed" });
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
{"type":"subscribe","channel":"updates"}
|
|
42
|
-
```
|
|
39
|
+
await plurnk.edit({ path: "wss://api.example.com/feed", content: "{\"type\":\"subscribe\",\"channel\":\"updates\"}" });
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
{"type":"ping"}
|
|
46
|
-
```
|
|
41
|
+
await plurnk.send({ target: "wss://api.example.com/feed", text: "{\"type\":\"ping\"}" });
|
|
47
42
|
|
|
48
43
|
Connection identity includes the workspace, exact `ws`/`wss` protocol, host,
|
|
49
44
|
non-default port, path, and ordered query. A fragment does not change socket
|
|
@@ -52,4 +47,4 @@ identity; `messages` is the only current channel. An unavailable channel returns
|
|
|
52
47
|
|
|
53
48
|
The transport carries text frames only, in native order, one durable write at a
|
|
54
49
|
time; a binary inbound frame settles `415` and closes the socket; there is no
|
|
55
|
-
reconnection (
|
|
50
|
+
reconnection (`read` again after terminal cleanup) and no handshake metadata.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plurnk/plurnk-schemes-http",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "HTTP(S) request/response and WebSocket scheme handlers for the plurnk agent runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"plurnk",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"@plurnk/plurnk-schemes": "^3.0.0"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@plurnk/plurnk-contracts": "^7.
|
|
79
|
-
"@plurnk/plurnk-meta": "^2.
|
|
78
|
+
"@plurnk/plurnk-contracts": "^7.3.0",
|
|
79
|
+
"@plurnk/plurnk-meta": "^2.2.0",
|
|
80
80
|
"eventsource-parser": "^4.1.3"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "9d234b81e14a1d3fe88b9be3f7c643a0eb929533"
|
|
83
83
|
}
|