@kottera/tera-web 0.0.0-stage → 0.1.0-rc.2
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/LICENSE +201 -0
- package/NOTICE +28 -0
- package/README.md +37 -2
- package/dist/client.d.ts +138 -0
- package/dist/client.js +250 -0
- package/dist/feed/connection.d.ts +158 -0
- package/dist/feed/connection.js +427 -0
- package/dist/feed/sse.d.ts +51 -0
- package/dist/feed/sse.js +134 -0
- package/dist/generated/feed.d.ts +199 -0
- package/dist/generated/feed.js +16 -0
- package/dist/generated/kit.d.ts +246 -0
- package/dist/generated/kit.js +148 -0
- package/dist/generated/workorder-events.d.ts +47 -0
- package/dist/generated/workorder-events.js +10 -0
- package/dist/generated/workorder.d.ts +3522 -0
- package/dist/generated/workorder.js +74 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/routes.d.ts +10 -0
- package/dist/routes.js +32 -0
- package/package.json +32 -4
package/dist/feed/sse.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// The server-sent-events transport under the feed connection: D3's
|
|
2
|
+
// `GET /tera/v1/feeds/{feed}` is read with fetch rather than EventSource,
|
|
3
|
+
// because EventSource hides the HTTP status a refusal is told apart by and
|
|
4
|
+
// reconnects on its own schedule, which is the connection's to decide
|
|
5
|
+
// (ADR-162 D4 item 2).
|
|
6
|
+
/**
|
|
7
|
+
* SSEParser turns the text of an event stream, in whatever chunks it arrives,
|
|
8
|
+
* into events, as the WHATWG event-stream grammar reads it: `\n`, `\r\n` and
|
|
9
|
+
* `\r` all end a line, a blank line dispatches, a line starting `:` is a
|
|
10
|
+
* comment, and `data` lines join with `\n`. `id` and `retry` are read and
|
|
11
|
+
* dropped — the connection keeps its own cursor and its own backoff.
|
|
12
|
+
*/
|
|
13
|
+
export class SSEParser {
|
|
14
|
+
buffer = "";
|
|
15
|
+
event = "";
|
|
16
|
+
data = [];
|
|
17
|
+
push(chunk) {
|
|
18
|
+
this.buffer += chunk;
|
|
19
|
+
const out = [];
|
|
20
|
+
for (;;) {
|
|
21
|
+
const at = this.buffer.search(/\r\n|\r|\n/);
|
|
22
|
+
if (at < 0)
|
|
23
|
+
break;
|
|
24
|
+
// A `\r` at the very end may be the first half of a `\r\n` split across
|
|
25
|
+
// chunks: wait for the next one before reading the line.
|
|
26
|
+
if (this.buffer[at] === "\r" && at === this.buffer.length - 1)
|
|
27
|
+
break;
|
|
28
|
+
const width = this.buffer.startsWith("\r\n", at) ? 2 : 1;
|
|
29
|
+
const line = this.buffer.slice(0, at);
|
|
30
|
+
this.buffer = this.buffer.slice(at + width);
|
|
31
|
+
this.line(line, out);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
line(line, out) {
|
|
36
|
+
if (line === "") {
|
|
37
|
+
if (this.data.length > 0) {
|
|
38
|
+
out.push({ event: this.event || "message", data: this.data.join("\n") });
|
|
39
|
+
}
|
|
40
|
+
this.event = "";
|
|
41
|
+
this.data = [];
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (line.startsWith(":"))
|
|
45
|
+
return;
|
|
46
|
+
const colon = line.indexOf(":");
|
|
47
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
48
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
49
|
+
if (value.startsWith(" "))
|
|
50
|
+
value = value.slice(1);
|
|
51
|
+
if (field === "event")
|
|
52
|
+
this.event = value;
|
|
53
|
+
else if (field === "data")
|
|
54
|
+
this.data.push(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** A feed route that answered with something other than an event stream. */
|
|
58
|
+
export class FeedOpenError extends Error {
|
|
59
|
+
/** The HTTP status the route answered with. */
|
|
60
|
+
status;
|
|
61
|
+
/** The answer's content type. */
|
|
62
|
+
contentType;
|
|
63
|
+
/** As much of the body as was read: the kit's error envelope, on a refusal (ADR-162 D2 item 8). */
|
|
64
|
+
body;
|
|
65
|
+
constructor(status, contentType, body) {
|
|
66
|
+
super(`feed route answered HTTP ${status} (${contentType || "no content type"})${body ? ": " + body : ""}`);
|
|
67
|
+
this.name = "FeedOpenError";
|
|
68
|
+
this.status = status;
|
|
69
|
+
this.contentType = contentType;
|
|
70
|
+
this.body = body;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The most of a refusal's body kept on a {@link FeedOpenError}. */
|
|
74
|
+
const BODY_LIMIT = 2048;
|
|
75
|
+
/**
|
|
76
|
+
* isRefusal reports whether an open failed in a way trying again cannot fix,
|
|
77
|
+
* so the connection stops rather than retrying it (ADR-162 D4 item 2). A 4xx
|
|
78
|
+
* is a refusal — the person may not, or the feed is not there — except 408 and
|
|
79
|
+
* 429, which say "later". So is 501, and so is a success that is not an event
|
|
80
|
+
* stream: that is a page served where the kit's route should be, which no
|
|
81
|
+
* retry turns into a feed. Anything else — a 5xx, a dropped connection — is a
|
|
82
|
+
* transport failure, and is retried.
|
|
83
|
+
*/
|
|
84
|
+
export function isRefusal(err) {
|
|
85
|
+
if (!(err instanceof FeedOpenError))
|
|
86
|
+
return false;
|
|
87
|
+
const { status } = err;
|
|
88
|
+
if (status >= 200 && status < 300)
|
|
89
|
+
return true;
|
|
90
|
+
if (status === 501)
|
|
91
|
+
return true;
|
|
92
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* fetchOpener is the {@link FeedOpener} a browser uses: a same-origin `GET`
|
|
96
|
+
* read as an event stream. `fetchFn` is injectable so the transport itself is
|
|
97
|
+
* tested without a network.
|
|
98
|
+
*/
|
|
99
|
+
export function fetchOpener(fetchFn = (...args) => globalThis.fetch(...args)) {
|
|
100
|
+
return async (url, onEvent, signal) => {
|
|
101
|
+
const response = await fetchFn(url, {
|
|
102
|
+
method: "GET",
|
|
103
|
+
headers: { Accept: "text/event-stream" },
|
|
104
|
+
credentials: "same-origin",
|
|
105
|
+
cache: "no-store",
|
|
106
|
+
signal,
|
|
107
|
+
});
|
|
108
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
109
|
+
if (!response.ok || !contentType.toLowerCase().startsWith("text/event-stream") || response.body === null) {
|
|
110
|
+
let body = "";
|
|
111
|
+
try {
|
|
112
|
+
body = (await response.text()).slice(0, BODY_LIMIT);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// The body is evidence, not the verdict: the status decides.
|
|
116
|
+
}
|
|
117
|
+
throw new FeedOpenError(response.status, contentType, body);
|
|
118
|
+
}
|
|
119
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
120
|
+
const parser = new SSEParser();
|
|
121
|
+
try {
|
|
122
|
+
for (;;) {
|
|
123
|
+
const { done, value } = await reader.read();
|
|
124
|
+
if (done)
|
|
125
|
+
return;
|
|
126
|
+
for (const e of parser.push(value))
|
|
127
|
+
onEvent(e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
reader.releaseLock();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed connect-time parameters.
|
|
3
|
+
*
|
|
4
|
+
* The query parameters of a feed URL (`GET /feeds/<name>?…`), each a repeatable query key. The query is a closed vocabulary: an unknown parameter is refused at connect.
|
|
5
|
+
*
|
|
6
|
+
* Schema: sdk/feed/schema/v1/connect.params.json
|
|
7
|
+
*/
|
|
8
|
+
export type FeedConnectParams = {
|
|
9
|
+
/** Precedes the live stream with the stored history of the selected run. */
|
|
10
|
+
backfill?: "all";
|
|
11
|
+
/** Overrides the delivery window: a duration such as `2s`, or `0` for no batching. */
|
|
12
|
+
batch_window?: string;
|
|
13
|
+
/** Selects a subset of the topics the connection would otherwise receive; each occurrence may be a comma-separated list. */
|
|
14
|
+
topics?: string[];
|
|
15
|
+
/** Adds or tightens one predicate, `<field>=<value>[,<value>…]`, once per field. */
|
|
16
|
+
where?: string[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The vocabulary of the feed drop reason (sdk/feed.DropReason).
|
|
20
|
+
*
|
|
21
|
+
* Every member the daemon writes. A later level adds members and removes none (ADR-143 D3); how a consumer reads an unknown member is ADR-143 D13's.
|
|
22
|
+
*
|
|
23
|
+
* Schema: sdk/feed/schema/v1/drop-reason.json
|
|
24
|
+
*/
|
|
25
|
+
export type FeedDropReason = "slow_consumer" | "rate_limit" | "connection_closed";
|
|
26
|
+
/**
|
|
27
|
+
* The vocabulary of the feed frame `type` (sdk/feed.FrameType).
|
|
28
|
+
*
|
|
29
|
+
* Every member the daemon writes. A later level adds members and removes none (ADR-143 D3); how a consumer reads an unknown member is ADR-143 D13's.
|
|
30
|
+
*
|
|
31
|
+
* Schema: sdk/feed/schema/v1/frame-type.json
|
|
32
|
+
*/
|
|
33
|
+
export type FeedFrameType = "record" | "status" | "heartbeat" | "batch";
|
|
34
|
+
/**
|
|
35
|
+
* `batch` frame (tera.feed.v1).
|
|
36
|
+
*
|
|
37
|
+
* A `batch` frame, generated from feed.BatchFrame.
|
|
38
|
+
*
|
|
39
|
+
* Schema: sdk/feed/schema/v1/frame.batch.json
|
|
40
|
+
*/
|
|
41
|
+
export type FeedFrameBatch = {
|
|
42
|
+
/** The frame's type; a consumer skips a frame whose type it does not know (ADR-143 D13). */
|
|
43
|
+
type: string;
|
|
44
|
+
/** The feed's name. */
|
|
45
|
+
feed: string;
|
|
46
|
+
/** This frame's own place in the connection's sequence; the records inside precede it. */
|
|
47
|
+
seq: number;
|
|
48
|
+
/** When the daemon wrote the frame, RFC 3339. */
|
|
49
|
+
at: string;
|
|
50
|
+
/** The delivery window in force, in seconds. */
|
|
51
|
+
window_seconds: number;
|
|
52
|
+
/** The number of records in the batch. */
|
|
53
|
+
count: number;
|
|
54
|
+
/** The window's records, in order; each element is a record frame. */
|
|
55
|
+
records: unknown[] | null;
|
|
56
|
+
/** Records dropped during this window. */
|
|
57
|
+
dropped?: number;
|
|
58
|
+
/** The connection's running total of dropped records. */
|
|
59
|
+
dropped_total?: number;
|
|
60
|
+
/** Why records were dropped; a member of the drop-reason vocabulary. */
|
|
61
|
+
drop_reason?: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* `heartbeat` frame (tera.feed.v1).
|
|
65
|
+
*
|
|
66
|
+
* A `heartbeat` frame, generated from feed.HeartbeatFrame.
|
|
67
|
+
*
|
|
68
|
+
* Schema: sdk/feed/schema/v1/frame.heartbeat.json
|
|
69
|
+
*/
|
|
70
|
+
export type FeedFrameHeartbeat = {
|
|
71
|
+
/** The frame's type; a consumer skips a frame whose type it does not know (ADR-143 D13). */
|
|
72
|
+
type: string;
|
|
73
|
+
/** The feed's name. */
|
|
74
|
+
feed: string;
|
|
75
|
+
/** This frame's place in the connection's single sequence, which has no gaps. */
|
|
76
|
+
seq: number;
|
|
77
|
+
/** When the daemon wrote the frame, RFC 3339. */
|
|
78
|
+
at: string;
|
|
79
|
+
/** How long the connection has been idle, in seconds. */
|
|
80
|
+
idle_seconds: number;
|
|
81
|
+
/** The connection's running total of dropped records. */
|
|
82
|
+
dropped_total?: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* `record` frame (tera.feed.v1).
|
|
86
|
+
*
|
|
87
|
+
* A `record` frame, generated from feed.RecordFrame.
|
|
88
|
+
*
|
|
89
|
+
* Schema: sdk/feed/schema/v1/frame.record.json
|
|
90
|
+
*/
|
|
91
|
+
export type FeedFrameRecord = {
|
|
92
|
+
/** The frame's type; a consumer skips a frame whose type it does not know (ADR-143 D13). */
|
|
93
|
+
type: string;
|
|
94
|
+
/** The feed's name. */
|
|
95
|
+
feed: string;
|
|
96
|
+
/** This record's place in the connection's single sequence, which has no gaps. */
|
|
97
|
+
seq: number;
|
|
98
|
+
/** The broker topic the record was published on. */
|
|
99
|
+
topic: string;
|
|
100
|
+
/** When the daemon observed the record, RFC 3339. */
|
|
101
|
+
observed_at: string;
|
|
102
|
+
/** The publishing principal, when one is known (ADR-083 D11). */
|
|
103
|
+
publisher?: string;
|
|
104
|
+
/** Whether the publisher was verified rather than claimed (ADR-011 Amendment 1); always present. */
|
|
105
|
+
publisher_verified: boolean;
|
|
106
|
+
/** The content type the publishing adapter declared: raw, json, text, protobuf, jsonrpc, formdata, or unknown/<n>. A label, not the arbiter of which payload key is set. */
|
|
107
|
+
content_type: string;
|
|
108
|
+
/** The record's payload as JSON, when its content type is JSON; its shape is the publishing family's. */
|
|
109
|
+
payload?: unknown;
|
|
110
|
+
/** The record's payload, when it is valid UTF-8 that is not JSON. */
|
|
111
|
+
payload_text?: string;
|
|
112
|
+
/** The record's payload, base64-encoded, when it is neither JSON nor UTF-8 text. */
|
|
113
|
+
payload_base64?: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* `status` frame (tera.feed.v1).
|
|
117
|
+
*
|
|
118
|
+
* A `status` frame, generated from feed.StatusFrame.
|
|
119
|
+
*
|
|
120
|
+
* Schema: sdk/feed/schema/v1/frame.status.json
|
|
121
|
+
*/
|
|
122
|
+
export type FeedFrameStatus = {
|
|
123
|
+
/** The frame's type; a consumer skips a frame whose type it does not know (ADR-143 D13). */
|
|
124
|
+
type: string;
|
|
125
|
+
/** The status value; a consumer reads one it does not know as possible loss and reconciles (ADR-143 D13). */
|
|
126
|
+
status: string;
|
|
127
|
+
/** The feed's name. */
|
|
128
|
+
feed: string;
|
|
129
|
+
/** This frame's place in the connection's single sequence, which has no gaps. */
|
|
130
|
+
seq: number;
|
|
131
|
+
/** When the daemon wrote the frame, RFC 3339. */
|
|
132
|
+
at: string;
|
|
133
|
+
/** A human-readable explanation. Never the machine-readable answer: that is status. */
|
|
134
|
+
message?: string;
|
|
135
|
+
/** Accompanies open and partial_authorization. On open, the effective filter set this connection delivers, after any connect-time narrowing. */
|
|
136
|
+
filters?: string[];
|
|
137
|
+
/** Accompanies open and partial_authorization: the feed's filters the principal is not granted. */
|
|
138
|
+
denied?: string[];
|
|
139
|
+
/** On narrowed, the authorized filter set the connection was narrowed from. */
|
|
140
|
+
narrowed_from?: string[];
|
|
141
|
+
/** On narrowed, the effective predicates after the client's request was folded into the feed's own. */
|
|
142
|
+
where?: string[];
|
|
143
|
+
/** On open, the heartbeat interval in seconds: the consumer's silence budget. */
|
|
144
|
+
heartbeat_interval_seconds?: number;
|
|
145
|
+
/** On open, on a batching connection, the effective delivery window in seconds. */
|
|
146
|
+
batch_window_seconds?: number;
|
|
147
|
+
/** On open, on a batching connection, the ceiling on one batch's record count. */
|
|
148
|
+
batch_max_records?: number;
|
|
149
|
+
/** On records_dropped, the records dropped since the last report. */
|
|
150
|
+
dropped?: number;
|
|
151
|
+
/** The connection's running total of dropped records; also carried on every terminal frame. */
|
|
152
|
+
dropped_total?: number;
|
|
153
|
+
/** Why records were dropped; a member of the drop-reason vocabulary. */
|
|
154
|
+
drop_reason?: string;
|
|
155
|
+
/** On a terminal frame, the WebSocket close code that follows it. */
|
|
156
|
+
close_code?: number;
|
|
157
|
+
/** On backfill_gap and backfill_unavailable, the run the statement is about. */
|
|
158
|
+
run?: string;
|
|
159
|
+
/** On backfill_gap, the first day of the inclusive range the store no longer reaches. */
|
|
160
|
+
missing_from?: string;
|
|
161
|
+
/** On backfill_gap, the last day of the inclusive range the store no longer reaches. */
|
|
162
|
+
missing_to?: string;
|
|
163
|
+
/** On backfill_gap, the number of days in the missing range. */
|
|
164
|
+
missing_days?: number;
|
|
165
|
+
/** On backfill_gap, records lost inside days that were read. */
|
|
166
|
+
missing_records?: number;
|
|
167
|
+
/** On backfill_gap and backfill_unavailable, the machine-readable cause. Its vocabulary is not yet part of this contract. */
|
|
168
|
+
reason?: string;
|
|
169
|
+
/** On backfill_unavailable, the partition's admission sampling rate: one session in N is kept (ADR-114 D8). */
|
|
170
|
+
sampling_rate?: number;
|
|
171
|
+
/** On backfill_complete, how many records came from the store. */
|
|
172
|
+
records?: number;
|
|
173
|
+
/** On backfill_complete, how many sessions those records came from. */
|
|
174
|
+
sessions?: number;
|
|
175
|
+
/** On backfill_degraded, the stored record schema versions the daemon met and does not understand. */
|
|
176
|
+
schema_versions?: number[];
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* The vocabulary of the feed `status` value (sdk/feed.Status).
|
|
180
|
+
*
|
|
181
|
+
* Every member the daemon writes. A later level adds members and removes none (ADR-143 D3); how a consumer reads an unknown member is ADR-143 D13's.
|
|
182
|
+
*
|
|
183
|
+
* Schema: sdk/feed/schema/v1/status.json
|
|
184
|
+
*/
|
|
185
|
+
export type FeedStatus = "open" | "partial_authorization" | "narrowed" | "records_dropped" | "upstream_lost" | "shutting_down" | "selection_changed" | "credential_expired" | "backfill_complete" | "backfill_gap" | "backfill_unavailable" | "backfill_degraded";
|
|
186
|
+
/**
|
|
187
|
+
* Every type this module declares, mapped to the repository path of the schema
|
|
188
|
+
* it was generated from.
|
|
189
|
+
*/
|
|
190
|
+
export declare const FEED_SCHEMAS: {
|
|
191
|
+
readonly FeedConnectParams: "sdk/feed/schema/v1/connect.params.json";
|
|
192
|
+
readonly FeedDropReason: "sdk/feed/schema/v1/drop-reason.json";
|
|
193
|
+
readonly FeedFrameType: "sdk/feed/schema/v1/frame-type.json";
|
|
194
|
+
readonly FeedFrameBatch: "sdk/feed/schema/v1/frame.batch.json";
|
|
195
|
+
readonly FeedFrameHeartbeat: "sdk/feed/schema/v1/frame.heartbeat.json";
|
|
196
|
+
readonly FeedFrameRecord: "sdk/feed/schema/v1/frame.record.json";
|
|
197
|
+
readonly FeedFrameStatus: "sdk/feed/schema/v1/frame.status.json";
|
|
198
|
+
readonly FeedStatus: "sdk/feed/schema/v1/status.json";
|
|
199
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Code generated by cmd/internal/genwebtypes from sdk/feed/schema/v1/; DO NOT EDIT.
|
|
2
|
+
// Regenerate with `make web-types` (ADR-162 D4 item 1).
|
|
3
|
+
/**
|
|
4
|
+
* Every type this module declares, mapped to the repository path of the schema
|
|
5
|
+
* it was generated from.
|
|
6
|
+
*/
|
|
7
|
+
export const FEED_SCHEMAS = {
|
|
8
|
+
FeedConnectParams: "sdk/feed/schema/v1/connect.params.json",
|
|
9
|
+
FeedDropReason: "sdk/feed/schema/v1/drop-reason.json",
|
|
10
|
+
FeedFrameType: "sdk/feed/schema/v1/frame-type.json",
|
|
11
|
+
FeedFrameBatch: "sdk/feed/schema/v1/frame.batch.json",
|
|
12
|
+
FeedFrameHeartbeat: "sdk/feed/schema/v1/frame.heartbeat.json",
|
|
13
|
+
FeedFrameRecord: "sdk/feed/schema/v1/frame.record.json",
|
|
14
|
+
FeedFrameStatus: "sdk/feed/schema/v1/frame.status.json",
|
|
15
|
+
FeedStatus: "sdk/feed/schema/v1/status.json",
|
|
16
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { FeedFrameRecord, FeedFrameStatus } from "./feed.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The vocabulary of the kit's own error codes (sdk/plugin/webui.KitCodes).
|
|
4
|
+
*
|
|
5
|
+
* Every code the kit answers with when no verb gave a verdict. Only tera_unavailable is retryable. A failure envelope may also carry a verb's own code, or tera's own code for a call it refused, verbatim, which is not a member: a reader keeps a code it does not know as it arrived and branches on retryable.
|
|
6
|
+
*
|
|
7
|
+
* Schema: sdk/plugin/webui/schema/v1/error-code.json
|
|
8
|
+
*/
|
|
9
|
+
export type KitErrorCode = "unauthenticated" | "cross_origin" | "route_not_found" | "method_not_allowed" | "route_not_served" | "invalid_request" | "exchange_refused" | "tera_unavailable";
|
|
10
|
+
/**
|
|
11
|
+
* Kit error envelope.
|
|
12
|
+
*
|
|
13
|
+
* Every kit route's failure (ADR-162 D2 item 8). The outer form is ADR-035's management-API envelope.
|
|
14
|
+
*
|
|
15
|
+
* Schema: sdk/plugin/webui/schema/v1/error.json
|
|
16
|
+
*/
|
|
17
|
+
export type KitError = {
|
|
18
|
+
/** The failure. */
|
|
19
|
+
error: {
|
|
20
|
+
/** What failed: one of the kit's own codes (the error-code vocabulary) when the kit refused before any verb ran, tera's own code, verbatim, when tera refused a call the kit relayed, or a verb's own code, verbatim, when a verb refused. A code a reader does not know is kept as it arrived. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** A sentence for a person saying what failed. Never carries a secret. */
|
|
23
|
+
message: string;
|
|
24
|
+
/** Whether the identical request may succeed if it is sent again later with nothing changed. A reader branches on this, never on a list of codes. */
|
|
25
|
+
retryable: boolean;
|
|
26
|
+
/** How long to wait before retrying, in milliseconds, when the refusing verb said. */
|
|
27
|
+
retry_after_ms?: number;
|
|
28
|
+
/** Structured detail the refusing verb attached, whose shape the verb decides. The kit's own refusals attach none. */
|
|
29
|
+
data?: unknown;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The feed's reauthenticate event.
|
|
34
|
+
*
|
|
35
|
+
* The data of the last event of a feed stream closed because the person's token expired.
|
|
36
|
+
*
|
|
37
|
+
* Schema: sdk/plugin/webui/schema/v1/feed.event.reauthenticate.json
|
|
38
|
+
*/
|
|
39
|
+
export type KitFeedEventReauthenticate = {
|
|
40
|
+
/** Why the stream closed. token_expired is the reason this version sends; a reader reconnects at once whatever the reason. */
|
|
41
|
+
reason: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* POST /tera/v1/invoke/{capability} response.
|
|
45
|
+
*
|
|
46
|
+
* The verb's reply envelope, HTTP 200 whenever the verb ran: workorder.HTTPInvoker's reading of the management invoke route.
|
|
47
|
+
*
|
|
48
|
+
* Schema: sdk/plugin/webui/schema/v1/invoke.response.json
|
|
49
|
+
*/
|
|
50
|
+
export type KitInvokeResponse = {
|
|
51
|
+
/** success when the verb answered; error when it refused. */
|
|
52
|
+
status: "success" | "error";
|
|
53
|
+
/** The verb's answer, present when status is success. */
|
|
54
|
+
result?: {
|
|
55
|
+
/** The verb's result. For a work_order.* verb, valid against sdk/workorder/schema/v1/<verb>.result.json. */
|
|
56
|
+
structured: unknown;
|
|
57
|
+
};
|
|
58
|
+
/** The verb's refusal, present when status is error. */
|
|
59
|
+
error?: {
|
|
60
|
+
code: string;
|
|
61
|
+
message: string;
|
|
62
|
+
retryable: boolean;
|
|
63
|
+
retry_after_ms?: number;
|
|
64
|
+
/** Structured detail the refusing tool attached, whose shape the tool and its code decide. */
|
|
65
|
+
data?: unknown;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* GET /tera/v1/me response.
|
|
70
|
+
*
|
|
71
|
+
* The signed-in person.
|
|
72
|
+
*
|
|
73
|
+
* Schema: sdk/plugin/webui/schema/v1/me.response.json
|
|
74
|
+
*/
|
|
75
|
+
export type KitMeResponse = {
|
|
76
|
+
/** The signed-in person, as tera's caller assertion names them: spelled as a Work Order that person filed would name them. */
|
|
77
|
+
subject: string;
|
|
78
|
+
/** Where to send the person to sign out: tera's own path, which ends their session for every plugin. */
|
|
79
|
+
signout_path: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* POST /tera/v1/uploads response.
|
|
83
|
+
*
|
|
84
|
+
* The staged artifact an upload made (ADR-123).
|
|
85
|
+
*
|
|
86
|
+
* Schema: sdk/plugin/webui/schema/v1/upload.response.json
|
|
87
|
+
*/
|
|
88
|
+
export type KitUploadResponse = {
|
|
89
|
+
/** The artifact handle, which a Work Order's attachments name. */
|
|
90
|
+
handle: string;
|
|
91
|
+
/** The file's name, as the upload gave it. */
|
|
92
|
+
filename: string;
|
|
93
|
+
/** The file's media type, as the upload gave it. */
|
|
94
|
+
mime_type: string;
|
|
95
|
+
/** The file's length in bytes. */
|
|
96
|
+
size_bytes: number;
|
|
97
|
+
/** The sha256 of the bytes tera stored, as sha256:<hex>. */
|
|
98
|
+
digest: string;
|
|
99
|
+
/** The file's description, when the upload gave one. */
|
|
100
|
+
description?: string;
|
|
101
|
+
/** When the staged artifact is discarded if no Work Order has attached it, in RFC 3339. */
|
|
102
|
+
staged_expires_at: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Every type this module declares, mapped to the repository path of the schema
|
|
106
|
+
* it was generated from.
|
|
107
|
+
*/
|
|
108
|
+
export declare const KIT_SCHEMAS: {
|
|
109
|
+
readonly KitErrorCode: "sdk/plugin/webui/schema/v1/error-code.json";
|
|
110
|
+
readonly KitError: "sdk/plugin/webui/schema/v1/error.json";
|
|
111
|
+
readonly KitFeedEventReauthenticate: "sdk/plugin/webui/schema/v1/feed.event.reauthenticate.json";
|
|
112
|
+
readonly KitInvokeResponse: "sdk/plugin/webui/schema/v1/invoke.response.json";
|
|
113
|
+
readonly KitMeResponse: "sdk/plugin/webui/schema/v1/me.response.json";
|
|
114
|
+
readonly KitUploadResponse: "sdk/plugin/webui/schema/v1/upload.response.json";
|
|
115
|
+
};
|
|
116
|
+
/** The prefix every kit route is served under (ADR-162 D3). */
|
|
117
|
+
export declare const KIT_ROUTE_PREFIX = "/tera/v1/";
|
|
118
|
+
/** tera's own sign-out path, which `GET /tera/v1/me` answers as signout_path. */
|
|
119
|
+
export declare const KIT_SIGNOUT_PATH = "/_tera/signout";
|
|
120
|
+
/** The kit's route table (ADR-162 D3), as sdk/plugin/webui declares it. */
|
|
121
|
+
export declare const KIT_ROUTE_TABLE: {
|
|
122
|
+
readonly prefix: "/tera/v1/";
|
|
123
|
+
readonly signout_path: "/_tera/signout";
|
|
124
|
+
readonly same_origin: "A route marked same_origin refuses a request unless it is same-origin: when the request carries Sec-Fetch-Site, that header must be same-origin; when it does not, its Origin must equal the request's own (scheme http or https, and the Host the request was sent to). The refusal is 403 cross_origin.";
|
|
125
|
+
readonly public: "None. Every route under the prefix refuses a request with no verified caller, 401 unauthenticated, before anything else is decided. Only the app's assets, outside the prefix, are served to an anonymous request.";
|
|
126
|
+
readonly error: {
|
|
127
|
+
readonly media_type: "application/json";
|
|
128
|
+
readonly schema: "https://kottera.dev/schemas/plugin/webui/error/v1";
|
|
129
|
+
readonly description: "Every failure of every route: the kit's own refusal, under one of the error-code vocabulary, or a verb's refusal passed through as the verb gave it.";
|
|
130
|
+
};
|
|
131
|
+
readonly routes: readonly [{
|
|
132
|
+
readonly name: "me";
|
|
133
|
+
readonly method: "GET";
|
|
134
|
+
readonly path: "/tera/v1/me";
|
|
135
|
+
readonly summary: "Who is signed in, and where to send them to sign out.";
|
|
136
|
+
readonly same_origin: false;
|
|
137
|
+
readonly response: {
|
|
138
|
+
readonly media_type: "application/json";
|
|
139
|
+
readonly schema: "https://kottera.dev/schemas/plugin/webui/me.response/v1";
|
|
140
|
+
readonly description: "The signed-in person.";
|
|
141
|
+
};
|
|
142
|
+
}, {
|
|
143
|
+
readonly name: "invoke";
|
|
144
|
+
readonly method: "POST";
|
|
145
|
+
readonly path: "/tera/v1/invoke/{capability}";
|
|
146
|
+
readonly summary: "Invoke one capability as the person, with the request body as its params. tera decides it on the person's token; the kit vets nothing.";
|
|
147
|
+
readonly same_origin: true;
|
|
148
|
+
readonly path_params: readonly [{
|
|
149
|
+
readonly name: "capability";
|
|
150
|
+
readonly description: "The capability to invoke, e.g. work_order.get.";
|
|
151
|
+
readonly required: true;
|
|
152
|
+
}];
|
|
153
|
+
readonly request: {
|
|
154
|
+
readonly media_type: "application/json";
|
|
155
|
+
readonly description: "The capability's params. For a work_order.* verb, valid against sdk/workorder/schema/v1/<verb>.params.json.";
|
|
156
|
+
};
|
|
157
|
+
readonly response: {
|
|
158
|
+
readonly media_type: "application/json";
|
|
159
|
+
readonly schema: "https://kottera.dev/schemas/plugin/webui/invoke.response/v1";
|
|
160
|
+
readonly description: "The verb's reply envelope, HTTP 200 whenever the verb ran, refusals included.";
|
|
161
|
+
};
|
|
162
|
+
}, {
|
|
163
|
+
readonly name: "feed";
|
|
164
|
+
readonly method: "GET";
|
|
165
|
+
readonly path: "/tera/v1/feeds/{feed}";
|
|
166
|
+
readonly summary: "Open a feed as the person: server-sent events carrying feed records and status frames verbatim.";
|
|
167
|
+
readonly same_origin: false;
|
|
168
|
+
readonly path_params: readonly [{
|
|
169
|
+
readonly name: "feed";
|
|
170
|
+
readonly description: "The feed's name.";
|
|
171
|
+
readonly required: true;
|
|
172
|
+
}];
|
|
173
|
+
readonly query: readonly [{
|
|
174
|
+
readonly name: "topics";
|
|
175
|
+
readonly description: "Selects a subset of the topics the person may subscribe to; each occurrence may be a comma-separated list. Widening is refused.";
|
|
176
|
+
readonly repeated: true;
|
|
177
|
+
}, {
|
|
178
|
+
readonly name: "backfill";
|
|
179
|
+
readonly description: "Precede the live stream with the stored history of the run the connection selected.";
|
|
180
|
+
readonly values: readonly ["all"];
|
|
181
|
+
}];
|
|
182
|
+
readonly response: {
|
|
183
|
+
readonly media_type: "text/event-stream";
|
|
184
|
+
readonly description: "A stream of the events below, until the person's token expires or either side closes it. Any other feed frame (a heartbeat, a batch, a type this kit does not know) is relayed verbatim as an unnamed event, read by its own type.";
|
|
185
|
+
};
|
|
186
|
+
readonly events: readonly [{
|
|
187
|
+
readonly name: "record";
|
|
188
|
+
readonly schema: "https://kottera.dev/schemas/feed/frame.record/v1";
|
|
189
|
+
readonly description: "A feed record frame, verbatim.";
|
|
190
|
+
}, {
|
|
191
|
+
readonly name: "status";
|
|
192
|
+
readonly schema: "https://kottera.dev/schemas/feed/frame.status/v1";
|
|
193
|
+
readonly description: "A feed status frame, verbatim; loss is reported here and nowhere else.";
|
|
194
|
+
}, {
|
|
195
|
+
readonly name: "reauthenticate";
|
|
196
|
+
readonly schema: "https://kottera.dev/schemas/plugin/webui/feed.event.reauthenticate/v1";
|
|
197
|
+
readonly description: "The last event of a stream closed because the person's token expired. Reconnect at once.";
|
|
198
|
+
}];
|
|
199
|
+
}, {
|
|
200
|
+
readonly name: "upload";
|
|
201
|
+
readonly method: "POST";
|
|
202
|
+
readonly path: "/tera/v1/uploads";
|
|
203
|
+
readonly summary: "Upload one file as the person (ADR-123): the kit streams the body once, digesting it, then authorizes and stores it.";
|
|
204
|
+
readonly same_origin: true;
|
|
205
|
+
readonly headers: readonly [{
|
|
206
|
+
readonly name: "Content-Type";
|
|
207
|
+
readonly description: "The file's media type.";
|
|
208
|
+
readonly required: true;
|
|
209
|
+
}, {
|
|
210
|
+
readonly name: "Tera-Upload-Filename";
|
|
211
|
+
readonly description: "The file's name, percent-encoded UTF-8.";
|
|
212
|
+
readonly required: true;
|
|
213
|
+
}, {
|
|
214
|
+
readonly name: "Tera-Upload-Description";
|
|
215
|
+
readonly description: "A description of the file, percent-encoded UTF-8.";
|
|
216
|
+
}];
|
|
217
|
+
readonly request: {
|
|
218
|
+
readonly media_type: "application/octet-stream";
|
|
219
|
+
readonly description: "The file's bytes, any media type. The body is read against its declared Content-Length, capped by the 33554432-byte (32 MiB) bound on an input artifact; a body with no declared length is held to the cap. A declaration over the cap is refused before the body is read, and a body is refused at the first byte past its bound, never after it is read whole.";
|
|
220
|
+
};
|
|
221
|
+
readonly response: {
|
|
222
|
+
readonly media_type: "application/json";
|
|
223
|
+
readonly schema: "https://kottera.dev/schemas/plugin/webui/upload.response/v1";
|
|
224
|
+
readonly description: "The staged artifact.";
|
|
225
|
+
};
|
|
226
|
+
}];
|
|
227
|
+
};
|
|
228
|
+
/** The name of a route in KIT_ROUTE_TABLE. */
|
|
229
|
+
export type KitRouteName = (typeof KIT_ROUTE_TABLE)["routes"][number]["name"];
|
|
230
|
+
/**
|
|
231
|
+
* The JSON body each route answers on success, by route name. A route not
|
|
232
|
+
* here answers a body that is not JSON.
|
|
233
|
+
*/
|
|
234
|
+
export type KitRouteResponses = {
|
|
235
|
+
me: KitMeResponse;
|
|
236
|
+
invoke: KitInvokeResponse;
|
|
237
|
+
upload: KitUploadResponse;
|
|
238
|
+
};
|
|
239
|
+
/** The data of each server-sent event a feed stream carries, by event name. */
|
|
240
|
+
export type KitFeedEvents = {
|
|
241
|
+
record: FeedFrameRecord;
|
|
242
|
+
status: FeedFrameStatus;
|
|
243
|
+
reauthenticate: KitFeedEventReauthenticate;
|
|
244
|
+
};
|
|
245
|
+
/** Every route's failure body. */
|
|
246
|
+
export type KitRouteError = KitError;
|